Northwind Pocket Sales: Field Sales for Windows Mobile-based Smartphones

 

Christian Forsberg
Business anyplace
Contribution from: Odyssey Software

April 2005

Applies to:
   Windows Mobile–based Smartphones
   Microsoft Visual Studio .NET 2003
   Microsoft .NET Compact Framework version 1.0 (Service Pack 2)
   Microsoft SQL Server 2000

Summary: Learn how to develop a mobile enterprise field sales application for Windows Mobile–based Smartphones by using Visual Studio .NET, .NET Compact Framework, and the Windows Mobile 2003 SDK. The source code in this article implements server components, a database, and a Windows Mobile–based Smartphone client. (41 printed pages)

Download Northwind Pocket Sales – Field Sales for Windows Mobile-based Smartphone.msi from the Microsoft Download Center.

Odyssey Software CFCOM enables transparent access to controls such as the Windows Media Player and objects such as the Pocket Outlook Object Model and ADOCE, and virtually any third-party COM or ActiveX component. CFCOM can be licensed from Odyssey Software, although the code sample in this article will work with the license key found in the source code.

Download cfcom.exe from the Microsoft Download Center.

Contents

Sample Setup
Introduction
Smartphones for Speed and Size
Simplified Field Sales Process
Server Walkthrough
Northwind Pocket Sales Application Client Walkthrough
Code Walkthrough
Conclusion

Sample Setup

To use the sample application, you'll need to set up a database and a Web server. The instructions in this article assume that you copy the sample files to the deployment computer.

Identifying Dependencies

The deployment computer must have the following products correctly installed:

Setting Up the Database

The file, install.bat, is used to automate the set up of the database (NorthwindX) in SQL Server 2000. Before this file runs, you should update this file to include the correct authentication information. If you want to use SQL Server authentication, you should only update the user name and password in two calls: setup.bat and importblobvalues.bat. To use Windows authentication, change the first parameter (SQLSERVER) to WINDOWS, and then update the user name and password. Again, be sure to do this on both calls.

Setting Up the Web Server

You need to create three virtual directories with the same name as the three folders named Sales, SalesWS, and ProductImage. Be sure to set up the security as your system requires. If you only want to require anonymous access, you need to enable it for all three directories and be sure to allow the default anonymous user in IIS (for example, IUSR_MACHINENAME) read and write access to the database (NorthwindX).

Building the Sample

You can use the following instructions to help build the sample.

To build the sample

  1. Open Visual Studio .NET 2003.
  2. Build the server-side samples (in the Sales and SalesWS folders).
  3. Open the client sample.
  4. Run the client sample from within the development environment, and then deploy it to the emulator or a physical device. A sample database file (NorthwindX.zip) is included in the sample project, and if that file is removed before deployment, a new database is created by synchronizing with the server (or desktop computer).

Introduction

This article builds on the topics presented in:

The first article describes key elements in how to develop mobile field service applications, and the second focuses more on developing these applications for Smartphone. The third focuses on developing a field sales application for Pocket PC, and it includes an introduction to the general aspects of field sales applications.

This article is about designing and developing a mobile field sales application based on the Microsoft .NET Framework, the Windows Mobile platform for Smartphones, and the Microsoft .NET Compact Framework. This article provides a sample solution that addresses the needs of the fictitious company Northwind Traders from a sales process and field sales point of view.

The Pocket PC field sales article is about how to support the business process with a Pocket PC based solution. This article is about how to support the same business process with Windows Mobile–based Smartphones. The idea is not to stack the two solutions against each other in a competing situation. Moreover, the idea is not to show how to migrate a Pocket PC application to a Windows Mobile–based Smartphone platform. Both platforms can successfully support most business scenarios, and it is important for any developer of mobile applications to master both platforms. The deciding factors that each company uses to choose one or the other, or even both, are numerous and include form factor (size and appearance), data input requirements, and storage and connectivity requirements. So, this article should be considered a stand-alone Windows Mobile-based Smartphone article.

Smartphones for Speed and Size

For the cell phone market in general, and the Smartphone market in particular, the main attributes of new devices are speed and size. With the inherited law (Moore's) from the computer industry, devices are becoming faster and smaller. When you are building mobile solutions for Smartphones, you need to add further to that speed and size — with regard to technology and to business.

The business uses of Smartphones are about becoming more efficient, productive, and profitable. For example, you can let the sales force sell more by enabling them to create new orders on location. You can try to make the sales process even simpler and enable the sales force to do more things in less time. You might actually look at speed as a way of compressing distance in time. The keyword is "compress." That goes for both the business process, which this article will cover in more detail, and the way you implement your mobile solutions for Smartphone.

The areas where you can compress your implementation are the following tiers:

  • Presentation
  • Logic
  • Data

Compressing the presentation tier, or the user interface, doesn't mean that you make things smaller to force the same amount of data onto the smaller screen. On the contrary, it means that you need to make the forms even simpler to include only the most important pieces of information. If you are making a choice to include or remove something from a form on a Smartphone — always remove. If you really need the extra information, add a new form with that information. As always, let the standard applications that come with the device guide you. If you need to find a solution for a specific data-entry problem, go through the forms of the standard applications and try to reuse a design that already exists. If you are a Pocket PC developer, you need to start thinking about how to create forms that support input with keys rather than a stylus. This consideration is something that can really change the way the forms work.

Moving on to the logic, or the code, of the application, there are many things you can do to optimize. For example, finding ways to reduce the size of code is always a good thing because smaller code runs faster. In addition, the loading of forms can take valuable processing time. There are steps you can take to minimize the form loading time (for example, see the article Improving Microsoft .NET Compact Framework-based Application Form Load Performance, but the real solution is to load the forms as few times as possible — preferably only one time. You can minimize the number of times that an application loads a form by using a forms cache. You can use a forms cache to manage the loading of forms for the application and to keep the forms loaded when the application needs them again. Handling the opening and closing of forms (making sure that only one form is shown at a time) is a common problem, and it calls for an efficient way of handling the order in which forms are shown (and closed) — a forms stack. The code sample accompanying this article includes a reusable class that is a combination of a forms cache and a forms stack. For details, see the "Code Walkthrough" section of this article.

You can also compress a lot in the data tier. The field sales article about Pocket PC discussed using a DataSet to store data. Considering the fact that a saved DataSet is really XML, compression quickly becomes highly desirable, especially on a device where memory is a critical resource. This article's code sample contains a reusable (open source) component that enables you to use the standard .zip algorithm in your applications. In the sample, the component is used to compress the DataSet into a standard .zip file in the device file system, but you can use the same component to compress any data in your application. For details, see the "Code Walkthrough" section of this article.

Simplified Field Sales Process

In the case of Northwind Traders, field sales workers can choose to use either Pocket PC or Smartphone clients to perform the main activities of their field sales business process. The field sales article about Pocket PC presented a sales business process. Just as technology is being compressed, the business processes need to follow. Changing the way that people do their business is much harder than changing technology solutions, but sometimes the support of intelligently implemented mobile solutions can help change the way people work. Because of the need to simplify the business process for salespeople who use Smartphones, the new process of the sales process looks like the following:

  1. Customer needs more products and places order.
  2. Order is placed in back office (or by field sales staff).
  3. Field sales staff retrieves (synchronizes) new order.
  4. Field sales staff makes sale to customer.
  5. Field sales staff sends (synchronizes) order to back office.
  6. Back office delivers and invoices order.
  7. Customer pays invoice.
  8. Order is closed.

The sample application for this article covers process steps 2 through 5. The first process step does not have any digital support, and process steps 6 through 8 are not covered by this article because these are common back-office functionalities. As process step 2 indicates, orders are normally placed in the back office, but the field sales staff can also create orders while they are mobile. If the latter is the case, process step 3 is not necessary.

Enabling the creation of new orders in the field not only cuts costs by increasing productivity, but also promotes increased sales that lead to higher revenue and profit.

The preceding sales process can be broken down to a number of use cases, as shown in Figure 1.

Figure 1. Use-case model

The use case for placing the new presales order is implemented as a server Web application, and all the other use cases are included in the client Smartphone application for the field sales staff. For more details about the architecture, please see the previous articles.

Next, you can look at the feature walkthrough of the back-office server application and then the client.

Server Walkthrough

For the personnel in the back office, a Web application has been prepared to create new sales order assignments. This Web application is a Microsoft ASP.NET application written with Microsoft Visual Studio .NET 2003 in C# with data in Microsoft SQL Server 2000.

The application shows how to support the initial steps in a sales order business process. The intention is not to show how this is done with regard to source code because the focus of this article is the mobile solution. However, for completeness, the walkthrough gives an understanding of how this step in the sales order business process is implemented. You can examine the source code in this article's sample code.

First Page

The first page shows a list of the sales orders and their respective statuses. The last order has the status Open, which means that it has not yet been delivered, as shown in Figure 2.

Click here for larger image

Figure 2. First page of the Web application. Click the thumbnail for a larger image.

When you click the order number of the open sales order, the details about that sales order appear, as shown in Figure 3. The details include information about the products to be sold (name, unit price, quantity, and discount).

Figure 3. Sales order details

Creation of a New Sales Order

On the first page, if you click Add new sales order, a wizard starts to create a new sales order. The first page, as shown in Figure 4, is to select a customer. Type part of the customer name, and then click Find. The search results appear.

Click here for larger image

Figure 4. Select a customer. Click the thumbnail for a larger image.

When you select one of the customers (in this walkthrough, White Clover Markets), select the other sales order information, as shown in Figure 5. You can select the date that the delivery is required and who is assigned to handle the sale. Click Next when you are finished.

Click here for larger image

Figure 5. Enter sales order information. Click the thumbnail for a larger image.

On the last wizard page, as shown in Figure 6, you can add the order details (information about the products). Each order detail has a unit price, a required quantity, and maybe a discount rate. When you select a product in the list, the normal unit price is suggested, but you can update it. You must enter quantity and discount amounts. If you do not want a discount, you must enter a zero. When you click New, the order detail is added to the list. The Delete link after each row removes the respective order detail.

Click here for larger image

Figure 6. Add product information with price, quantity, and discount. Click the thumbnail for a larger image.

When you click Finish, you return to the first page, and the sales order is ready to be transferred to the field worker.

Northwind Pocket Sales Application Client Walkthrough

The example client scenario is a Smartphone application written with Microsoft Visual Studio .NET 2003 in C# and targets the .NET Compact Framework

The application shows how to support a sales business process by using a mobile phone device. The design choices were made to align to the process as much as possible and thereby maximize efficiency for the field worker. The various design choices will be commented during the walkthrough of the application.

First Run

In a real-world scenario, the first run of the application on the device is probably made not by the end user, but by a system administrator. The inclusion of these screens in the walkthrough is mainly because this article has a developer focus, and they show how a distributed application can do a first synchronization of data.

The application enters an initialization phase the first time the application is run because, as indicated in Figure 7, the application cannot connect to the local database. The local database is a local XML file that is zipped to save space.

Figure 7. No database found on first run

After the first message, you are automatically taken to the Options dialog box, as shown in Figure 8. Here, you can enter the required connectivity settings before the first data transfer from server to client. Select Local.

Figure 8. Options dialog box with connectivity settings

In the Local Options dialog box, shown in Figure 9, you set the location and name of the local DataSet XML file. The Local database setting points to a local DataSet XML file that implements the actual database for this application. Select Create database.

Figure 9. Local options with suggested database name

In the Server Options dialog box, shown in Figure 10, you have the following options:

  • Server database (URL) URL for the DataSet Server CE Web Service, which is used to initialize a new database and to get reference data.
  • Server login (User) Default user name used when connecting the server.
  • Web Service (URL) URL for the XML Web service, which is used to synchronize sales orders.
  • Product images (URL) URL for the virtual directory on the Web server where product images reside.

The first time you use the application, you should initialize (create) a new database.

Figure 10. Server options

A wizard leads you through the pages of initializing a new database. The first page of the Synchronization wizard, shown in Figure 11, informs you that a new database is about to be initialized.

Figure 11. Synchronize wizard for creating new database

In the second page, as shown in Figure 12, you enter a valid user name and password. These network credentials are used when you connect to the DataSet Server CE XML Web service. The DataSet Server CE XML Web service impersonates the given user when connecting to the database. The new database (local DataSet XML file) is created when you press the Finish soft key.

Figure 12. Server authentication

You are kept updated on the progress of the synchronization. Synchronization is completed when the Reference tables pulled message appears, as indicated in Figure 13. When you press the Close soft key, you return to the main menu.

Figure 13. Synchronization is performed

The first time you start the application, and each subsequent time, you are presented with the main menu screen shown in Figure 14. The functionality of the application is aligned with the business process of the sales field worker. The first menu option (Sales) is for the sales process. The other options on the main menu include support functionality for looking up customer information (Customers) and product information (Products), synchronizing with the server (Synchronize), entering application settings (Options), and finding information about the application (About).

Figure 14. Main menu

You should take care not to include too much functionality so that the application is as easy to use as possible; more functionality usually means increased complexity.

Sales

Sales orders are either assigned from the back office or created locally by the field sales staff. Like server synchronization, the sales process is implemented through a wizard. The first page, shown in Figure 15, is to filter out relevant sales orders.

Figure 15. Search criteria for sales orders

Notice that the application prefilled the Date from and Date to fields to save you valuable time. By default, the application searches for the last month up to and including today. In your application, you might need some advanced data as well. Understanding the needs of users can save valuable time and prevent frustration in the majority of use cases.

Note The wizard menu shown in Figure 15 includes the vital Back and Cancel commands, which you can use to go back to previous wizard pages or to cancel the entire wizard process.

You enter search criteria to search for sales orders. Sales orders matching the criteria appear in page two of the wizard, as shown in Figure 16. You select one of the open service orders and then press the Next soft key. If the sales order has been added locally (that is, not synchronized from the server), you can delete it by pressing the Menu soft key, and then selecting Delete.

Figure 16. Found sales orders

The third wizard page, shown in Figure 17, displays more detailed information about the sales order, such as order number, customer name, order date, and required delivery date.

Figure 17. Detailed sales order information

At this point, as shown in Figure 18 and Figure 19, you can choose to view customer details by using the Action hardware key when the customer name has focus. Viewing customer details is useful in route planning and when the salesperson must call the customer prior to arriving at the customer's site. The customer data is presented in a scrollable form, much like the inbuilt Smartphone applications. Figure 18 shows the upper part of the form. When you press the down hardware key, the lower part of the form becomes visible, as shown in Figure 19.

Figures 18 and 19. Detailed customer information

If you want to create a new order, press the Menu soft key, and then select New in the first wizard page shown in Figure 15. The detailed sales information for a new order is shown in Figure 20.

Figure 20. Sales information for a new order

To search for and select the customer, press the Menu soft key, and then select Find Customer. (See the Customers section in this document for the illustrations of the Find Customer feature.)

The fourth wizard page, shown in Figure 21, includes the sales order rows or details, that is, the products to be sold. Each row includes the quantity and discount for each product.

Figure 21. Sales order details (products)

When you select an order detail with the up and down hardware buttons, and then you select the Edit menu option, you can edit the order detail in a screen similar to Figure 27. You can also remove details by selecting the row and then selecting the Delete menu option. (A confirmation message will appear.)

If you want to add a new product, you can select New, and the Order Detail wizard starts, as shown in Figure 22. The first page of this wizard allows you to search for a product by its name and category. After you enter the search criteria, press the Next soft key.

Figure 22. Search criteria for products

The products that fit the search criteria are presented in a list, as shown in Figure 23. You can view detailed product information by selecting the product and then selecting the View menu option.

Figure 23. Found products to add to order

The detailed product information, as shown in Figures 24 and 25, can be useful during the sales process if you need to verify the product's article number, supplier name, items in stock, items on order, and so on.

Figure 24 and 25. Detailed product information

If you press the Image soft key, you can view an image of the product, as shown in Figure 26. This device retrieves this image from a Web server, which is online; therefore, the device saves valuable storage space.

Figure 26. Product image

When you close this screen (by pressing the Done soft key), you are returned to the second page in the Order Detail wizard, as previously shown in Figure 23.

After you select a product and press the Next soft key, the order detail information displays, as shown in Figure 27. On this page, you can modify the product price, quantity, and discount rate. You can change the latter by using the left and right hardware keys, and the percentage will increase and decrease in steps of 5 (5, 10, 15, 20, and so on) because this increment reflects the normal rates that are used.

Figure 27. Order detail information

After you finish making changes, press the Finish soft key. The product is added to the Sales Order Details screen that was previously shown in Figure 21.

When you press the Next soft key from the fourth page of the Sales wizard, the shipping information appears, as shown in Figure 28. This information includes the freight amount for the shipment and the shipper to use.

Figure 28. Shipping information

When you press the Next soft key, a summary report of the service order appears, as shown in Figure 29. It shows a complete summary of the sales order information, including the order details (products) with quantities, prices, and discounts. You can show the total order amount, including freight, to the customer before you leave the site.

Figure 29. Sales order summary report

After you press the Next soft key, you can use the seventh page, as shown in Figure 30, to confirm the suggested customer contact name or enter a new name. You can also choose how the receipt should be delivered to the customer.

Figure 30. Client name and copy delivery

After you press the Next soft key, you will see the final page in the Sales wizard, shown in Figure 31. On this page, you can select a new status for the sales order. If you set the new status to Reported, the sales order can no longer be changed on the Smartphone. A sales order with the reported status means that it is ready to be sent back to the server during the next synchronization of sales orders.

Figure 31. Set sales order status

You can press Finish to close the Sales wizard and return to the main menu.

Customers

As a support function, you can search for customers using a two-page wizard. On the first page, shown in Figure 32, you can filter relevant customers by name and contact person.

Figure 32. Search criteria for customers

Customers matching the criteria are displayed on the second page of the wizard, shown in Figure 33. If you select the View menu option, the wizards displays the product details, as previously shown in Figure 18 and 19.

Figure 33. Found customers

You can also view the sales order for the customer by selecting the Orders menu option. This action starts the Sales wizard, as previously shown in Figure 15, with the Customer box prefilled, as shown in Figure 34.

Figure 34. Sales wizard for a specific customer

Products

As another support function, you can search for products using a two-page wizard. On the first page, shown in Figure 35, you can filter the relevant products by name and category.

Figure 35. Search criteria for products

Products that match the criteria are displayed on the second page of the wizard, as shown in Figure 36.

Figure 36. Found products

When you select the View menu option, the device displays the product details, as previously shown in Figure 24 and 25.

Synchronization

In the Synchronize wizard, whose first page is shown in Figure 37, the feature set related to synchronization supports three use cases:

  • Synchronize orders Synchronizes sales orders to and from the Smartphone.
  • Get reference data Pulls reference data, such as product information, to the Smartphone.
  • Initialize new database Creates a new database and gets data from the server.

Synchronize sales orders is the default (and most common) option, and that is the one you want to use, so press the Next soft key.

Figure 37. Synchronization wizard to get new sales orders

On the second page of the wizard, you can provide the user name and password. After you enter this information, press the Next soft key. The wizard completes the action by reporting the progress of the actual synchronization. The SalesWS Web service is called during the synchronization, and it returns open sales orders that are assigned to you, the current Smartphone user.

The last page of the wizard, shown in Figure 38, informs you that the sales orders have been synchronized. To close the Synchronization wizard and return to the main menu, press the Close soft key.

Figure 38. Sales orders synchronized

Options

When you select Options from the Pocket Sales screen, you can set options related to local and server settings, as shown in Figure 39. When using this feature in real-world applications, you should put some form of a lock on the Options feature. This prevents other users from changing the settings that would prevent the application from functioning correctly. On Smartphone, the lock may require the user to press buttons in a certain sequence for a particular screen.

Figure 39. Options feature and the Local and Server settings

The Local Options screen, as shown in Figure 40, includes the name of the employee who should be currently using the Smartphone. This setting is used when retrieving assigned sales orders. Path and file name of the local database also can be set at this time.

Figure 40. Local options

On the Server Options screen, as shown in Figure 41, the application offers four server options:

  • Server database (URL) URL for the DataSet Server CE Web Service, which is used to initialize a new database and to retrieve reference data.
  • Server login (User) The default user name that is used when connecting the server.
  • Web Service (URL) URL for the XML Web service, which is used to synchronize sales orders.
  • Product images (URL) URL for the virtual directly on the Web server where product images reside.

Figure 41. Server options

About

When you select About from the Pocket Sales screen, the About screen appears, as shown in Figure 42. This screen is intended as a sample. It may be a good idea to include a similar screen into your own applications, but the sample application is not copyrighted. You can download and use the source code.

Figure 42. About the application

On this screen, you can also include a link to a Web page with product or support information.

World-Ready

This completes the walkthrough of the application, but there is one more interesting technical feature of this sample application: to show how globalization (localization) support can be implemented by using the support in the .NET Compact Framework. When you change the Regional Settings for language to Portuguese (Brazil) and then restart the application, the complete application is translated.

The translated main menu from Figure 14 is shown in Figure 43. Note that the application title, the menu options, and the soft key are all translated.

Figure 43. Translated main menu

Figure 44 shows the translated first page of the Sales. Notice that the form title and controls are all translated in addition to the soft keys and the menu options. Note also that the suggested dates in the list are presented in the correct format for Brazil (day/month/year).

Figure 44. Translated first page in the Sales wizard

Figure 45 shows that even the notification messages are translated.

Figure 45. Translated notification message

Figure 46 shows the report labels (cliente, data de ordem, produto, and so on) are translated, and again the correct date format is used.

Figure 46. Translated second page in Sales wizard

Finally, Figure 47 shows that the sales order statuses (open, reported, and so on) are also translated.

Figure 47. Translated final page in Sales wizard

Code Walkthrough

The previous section provided an example client scenario for the Pocket Sales application. For details about the general parts of the code, please see the article Northwind Pocket Service: Field Service for Windows Mobile 2003 Software for Smartphones.

You can reuse most of the code in the sample application, and it is especially interesting to reuse the functionality related to caching and stacking forms, which is located in the FormCache class, because this functionality is probably needed in most enterprise applications. Another example is the GlobalHandler class. You can use this class to globalize (localize) applications easier.

On a device like the Smartphone, where memory is a scarce resource, data compression can be very useful. In this document, you will find out how to compress data using a reusable component. But first, you'll see how the data access and manipulation is completed.

Data Access

The data in the Pocket Sales application is stored in an XML file and, more specifically, a DataSet persisted with its schema. The data access logic can be kept similar to how it would look if the data was stored in a database. For example, retrieving a number of rows to display in a ListView looks like this.

Cursor.Current = Cursors.WaitCursor;
try
{
    // Get order list data
    using (SalesHandler salesHandler = new SalesHandler())
        drs = salesHandler.GetList((int)Common.SalesStatus.Open,
                txtDateFrom.Text, txtDateTo.Text, txtCustomer.Text);

    // Fill ListView
    lvwOrders.BeginUpdate();
    lvwOrders.Items.Clear();
    foreach (DataRow dr in drs)
    {
           lvi = new ListViewItem(((DateTime)
                                dr["OrderDate"]).ToShortDateString());
           lvi.SubItems.Add(dr.GetParentRow(
               "Customers2Orders")["CompanyName"].ToString());
           lvi.SubItems.Add(dr["OrderID"].ToString());
           lvi.SubItems.Add(dr["LocalInsert"].ToString());
           lvi.SubItems.Add(dr.GetParentRow(
               "Customers2Orders")["ContactName"].ToString());
        lvwOrders.Items.Add(lvi);
    }
    lvwOrders.EndUpdate();
}
catch (Exception)
{
    MessageBox.Show(GlobalHandler.Translate.Text(
        "MsgCantFindOrders", "Could not find any orders!"),
        this.Text);
}
Cursor.Current = Cursors.Default;

Note how the data access logic is separated from this code into a handler that is instantiated with the using keyword to enable it to be automatically Disposed. A status flag and the content of the controls (txtDateFrom and so on) are used as search parameters to the GetList method of the SalesHandler class.

public DataRow[] GetList(int status, string dateFrom,
                         string dateTo, string customer)
{
    string sql = "Status = " + status.ToString();
    if (dateFrom.Length  > 0)
        sql += " AND OrderDate >= '" + dateFrom + "'";
    if (dateTo.Length > 0)
        sql += " AND OrderDate <= '" + dateTo + "'";
    if (customer.Length > 0)
        sql += " AND Parent(Customers2Orders).CompanyName LIKE '%"
                       + customer + "%'";
    DataRow[] drs = ds.Tables["Orders"].Select(sql, "OrderDate");
    return drs;
}

The SQL where clause (sql) that is built depends on the parameters provided, and then it is used to make a selection of rows from the main DataSet (ds).

Data Manipulation

The Sales wizard (SalesForm) is a good example of how data manipulation is done. When you select New on the first page of the wizard, a new order is created.

using (SalesHandler salesHandler = new SalesHandler())
{
    orderID = (new Guid(OpenNETCF.GuidEx.NewGuid(
        ).ToByteArray())).ToString();
    DataRow dr = salesHandler.GetNew();
    dr["OrderID"] = orderID;
    dr["OrderDate"] = DateTime.Today;
    dr["Status"] = (int)Common.SalesStatus.Open;
    dr["LocalInsert"] = true;
    dr.EndEdit();
    salesHandler.AddNew(dr);
}

First, a new order identity (unique identifier) is generated. Then, a new order row is created (GetNew), and it is filled with new data. The editing of the new row finishes (EndEdit), and it is finally added to the order table (AddNew).

After the last page of the wizard (when you select Finish), the following code is executed.

try
{
    using (SalesHandler salesHandler = new SalesHandler())
    {
        DataRow dr = salesHandler.GetForID(orderID);
        dr["CustomerID"] = customerID;
        if (cboShipVia.SelectedIndex > -1)
            dr["ShipVia"] = cboShipVia.SelectedValue;
        dr["ContactName"] = txtCustomerName.Text;
        dr["CustomerCopy"] = cboCustomerCopy.SelectedIndex;
        dr["OrderedDate"] = DateTime.Today;
        if (cboStatus.SelectedIndex > 0)
            dr["Status"] = (int)Common.SalesStatus.Reported;
        dr.EndEdit();
        salesHandler.Save();
    }
}
catch (Exception)
{
    MessageBox.Show(GlobalHandler.Translate.Text("MsgCantSaveOrder", "Could not save order!"), this.Text);
}

The method to retrieve an order row (GetForID) from the sales handler (salesHandler) returns a DataRow object with the order. The fields in the DataRow (dr) are updated. After the update is complete, the DataRow's editing mode is stopped (EndEdit), and the changes are saved (Save).

Data Compression

It is very nice to ease data manipulation, but a DataSet persisted to an XML file takes a relatively large amount of memory on a Smartphone — but memory is a limited resource. One option to keep the DataSet and still minimize the need for memory is to compress the XML that is stored. The most common algorithm for compression is the one used for ordinary zip files. Is it possible to use the same logic for this application? Fortunately, there is a well-implemented zip library provided for free from IC#Code called SharpZipLib. This library provides all of the functionality you need to manipulate zip archive files.

To demonstrate how easily this library can be used, examine how the DataSet is retrieved from a file when the application starts.

DataSet ds = new DataSet();
FileStream fs = new FileStream("NorthwindX.xml",
    FileMode.Open, FileAccess.ReadWrite);
XmlTextReader xtr = new XmlTextReader(fs);
ds.ReadXml(xtr, XmlReadMode.ReadSchema);
xtr.Close();
fs.Close();

Note how an ordinary file stream is used to read the contents of the file into a DataSet. Now, to use the ShartZipLib library, the code to load the DataSet from the same XML file compressed into a zip archive would look like the following.

DataSet ds = new DataSet();
ZipInputStream fs = new ZipInputStream(File.OpenRead(
    "NorthwindX.zip"));
fs.GetNextEntry();
XmlTextReader xtr = new XmlTextReader(fs);
ds.ReadXml(xtr, XmlReadMode.ReadSchema);
xtr.Close();
fs.Close();

The original file stream is replaced with a stream provided by the SharpZipLib library. To get to the data, the first file in the archive (.zip) file must be selected (GetNextEntry).

A similar approach is used when saving the DataSet. First, examine the original code.

FileStream fs = new FileStream(("NorthwindX.xml",
    FileMode.Create, FileAccess.Write);
XmlTextWriter xtw = new XmlTextWriter(fs, System.Text.Encoding.Unicode);
this.databaseDataSet.WriteXml(xtw, XmlWriteMode.WriteSchema);
xtw.Close();
fs.Close();
ds = null;

Second, examine the code using SharpZipLib.

ZipOutputStream fs = new ZipOutputStream(File.Create("NorthwindX.zip"));
fs.PutNextEntry(new ZipEntry(Path.GetFileName("NorthwindX.xml")));
XmlTextWriter xtw = new XmlTextWriter(fs, System.Text.Encoding.Unicode);
this.databaseDataSet.WriteXml(xtw, XmlWriteMode.WriteSchema);
xtw.Close();
fs.Close();
ds = null;

Because XML is a format with a lot of overhead and because it is very well suited for compression (with small measures and with great help [like the zip library]), the space required on the Smartphone to store the XML file is now reduced by approximately 90 percent.

The SharpZipLib library has much more to offer, like encryption. The best way to learn about these features is to start using the library.

There are many possibilities in using compression (even with encryption) in your applications such as compressing data for communication, that is, synchronization using zipped data or downloading zipped files for device initialization. With some work, you may also use compression to reduce the network traffic when synchronizing the dynamic data with the server.

Light-Weight "Web Services"

One of the best wasy to standardized application integration is to use XML Web Services, as shown by the synchronization logic of the sample application (Pocket Sales). However, sometimes you can use something less sophisticated to communicate with a server. For example, the application offers a feature to show an image of a product, as shown in Figure 26. The HTTP protocol is used to retrieve the image that is directly streamed into a PictureBox control.

HttpWebRequest req = (HttpWebRequest)
    WebRequest.Create("https://server/productimages/" +
    lblProductNo.Text + ".jpg");
HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
picImage.Image = new System.Drawing.Bitmap(resp.GetResponseStream());

The product number (taken from the lblProductNo label) is used to form the complete URL for the image. The request (HttpWebRequest) and response (HttpWebResponse) objects are used to get the image as a stream. Because the Bitmap constructor can create a new bitmap using a stream, the final code is very straightforward. And because the image is never stored on the Smartphone, this approach is memory efficient. However, the application depends on an online connection. If no connection is available, the device cannot show the product. The user requirements state if this is an acceptable solution in a real-world scenario. You need to update the URL in the server options, as shown in Figure 41.

Similarly, a device can retrieve and directly use anything that resides on a Web server and does not have to locally store the information. However, with reference to the previous discussion on compression, there's no good way to compress images.

Form Cache (and Stack) Implementation

Each enterprise application that contains a larger amount of forms requires that the forms, and the memory they consume, can be managed in an efficient way. There are several ways to implement a form cache. One of the most important goals of the implementation in this application is to avoid form inheritance because this complicates the actual form design. Another goal is to allow for both caching forms (that is, loading them in and out of memory) in addition to handling the stacking of forms (that is, the order in which forms are shown and navigated). Therefore, the FormCache class should support both the caching and stacking of forms. The class begins like the following.

sealed class FormCache : CollectionBase
{
    private System.Collections.ArrayList stack = new ArrayList();
    public static readonly FormCache Instance = new FormCache();

    private FormCache()
    {
    }

    private Form rootForm;
    public Form RootForm
    {
        get { return rootForm; }
        set { rootForm = value; }
    }

    private bool activation = false;
    public bool Activation
    {
        get { return activation; }
    }

The inheritance from the base collection (CollectionBase) provides the application's form cache with the actual cache in the form of a collection list (List), and a private variable (stack) provides the stacking of forms. The form cache is implemented as a singleton class, as shown by the private constructor and the public Instance property. The two properties are used for storing the root form (RootForm) and the value whether activation of forms is enabled (see the following code for the form's activated event).

In the main form (MainForm) of the application, you need to set the root form of the form cache first. This action also loads the singleton instance of the form cache, and it looks like the following.

FormCache.Instance.RootForm = this;

This form is not part of the cache or stack, but it is handled as an ordinary form that is always loaded as long as the application lives, but it is only visible when no other form is visible.

The following code provides two methods for caching forms. This code is the first one.

public Form Load(Type formType)
{
    // Check if it's in the cache already, and if so, return instance
    foreach(Form f in List)
        if(f.GetType().Name.Equals(formType.Name))
            return f;

    // Create new instance, translate, add to collection, and return
    Form form = (Form)Activator.CreateInstance(formType);
    GlobalHandler.Translate.Form(form);
    List.Add(form);
    return form;
}

The collection (List) loops to find out if the form is already loaded. If so, the already loaded form is returned. If not, the form is loaded using the activator, and at that point, the form is also translated before it is added to the cache collection (List) and then returned.

Even though a form push (see the following code) implicitly calls this method to load a form, this method is made public to allow the developer to choose when forms are loaded. For example, this choice allows for all of the forms to be loaded when the application starts to maximize performance after the application has started.

There are two methods for disposing forms.

public void DisposeForm(Type formType)
{
    foreach(Form f in List)
        if(f.GetType().Name.Equals(formType.Name))
        {
            f.Dispose();
            List.Remove(f);
            break;
        }
}
public void DisposeAllForms()
{
    foreach(Form f in List)
        f.Dispose();

    List.Clear();
}

There is one method for the disposing of one form and another method for all of the forms. The Dispose method of each form is called, and then the form is removed from the cache collection (List). Note that the stack (push and pop, see the following code) is not affected by the loading and disposing of forms.

The pushing of new forms on the stack is implemented like the following.

public void Push(Type formType)
{
    Form currentForm = null;

    try
    {
        // Only allow one push at a time to maintain stack integrity
        Monitor.Enter(this);

        Cursor.Current = Cursors.WaitCursor;

        // Find parent (current) form
        if(stack.Count < 1)
            currentForm = rootForm;
        else
            foreach(Form f in List)
            {
                // Find the last form in the stack
                if(f.GetType().Name.Equals(stack[stack.Count - 1]))
                {
                    currentForm = f;
                    break;
                }
            }

        // Stack new form
        activation = false;
        Form form = Load(formType);
        activation = true;
        form.Visible = true;
        stack.Add(formType.Name);
        currentForm.Visible = false;
        activation = false;
    }
    catch(Exception)
    {
        throw;
    }
    finally
    {
        Monitor.Exit(this);
    }
}

You can use the threading monitor to make sure that only one push is processed at a time. When the current (visible) form is found, the new form is cached (or retrieved from the cache — for details see the Load method earlier in this article), shown, and added to the stack. Finally, the current form is hidden.

To pop (remove) forms from the stack looks like the following.

public void Pop(int formsToPop)
{
    activation = true;

    // Remove all forms in cache?
    if(formsToPop >= stack.Count)
    {
        stack.Clear();
        rootForm.Visible = true;
        foreach(Form f in List)
            f.Visible = false;
    }
    else
    {
        // Remove from stack but not from cache
        for(int i = 0; i < formsToPop; i++)
            stack.RemoveAt(stack.Count - 1);

        Form currentForm = null;
        foreach(Form f in List)
        {
            // Current form?
            if(f.Visible)
              currentForm = f;

            // Last form in the stack?
            if(f.GetType().Name.Equals(stack[stack.Count - 1]))
                f.Visible = true;
        }
        currentForm.Visible = false;
    }
    activation = false;
}

Because the method takes a number of forms to pop as an argument, it can be used to pop several forms from the stack at once. If the number of forms to pop is greater than the available number of forms on the stack, the stack is cleared (all items are removed), the root form is shown, and all other forms in the cache (List) are hidden.

This concludes the walkthrough of the FormCache class. Next, you'll see how this class can be used and what demands it puts on the forms that are managed by the form cache.

Using the Form Cache/Stack

To show a new form, or to actually push a new form on the form stack, the code looks like the following.

FormCache.Instance.Push(typeof(SalesForm));

The push implicitly loads the form if it is not already loaded. This action can be done after the form has been requested (for example, by means of the user selecting a menu option) or the first time the application is started. In most real-world scenarios, performance when starting the application the first time is not critical, which is the reason why it's a good time to load all forms (or at least the most commonly used ones) of the application. After you've finished loading the forms, the application will be much more responsive.

If any parameters need to be passed to a new form, the code looks like the following.

parentForm = true;
OptionsForm optionsForm = (OptionsForm)
    FormCache.Instance.Load(typeof(OptionsForm));
optionsForm.DatabaseExist = databaseExist;
FormCache.Instance.Push(typeof(OptionsForm));

The boolean private form variable (parentForm) is used to indicate that this form is now acting as a parent form, that is, it has shown another (child) form. It is used in the activation (Activated) event to prevent normal activation (reset) of the form status (see the following code). The form is explicitly loaded to get the form instance, and, in this case, the purpose is to set the property flag that indicates if a database exist (DatabaseExist). When the property is set, the form is pushed as before.

Remember that in contrast to loading child forms in a normal way (like using the ShowDialog method), the execution of the parent form does not stop when it uses the form cache. Therefore, code that needs to be executed when the child form completes (is hidden), needs to be handled when the parent form is reactivated, as shown in the following code.

There are a few requirements on a form that will be used with the form cache. One requirement is that the activation event (Activated) needs to be implemented. It could look like the following.

private void AnyForm_Activated(object sender, EventArgs e)
{
    if(!FormCache.Instance.Activation)
        return;

    if(!parentForm)
    {
        // Reset controls
        txtName.Text = "";
        lvwItems.Items.Clear();
    }
    else
        parentForm = false;

    Cursor.Current = Cursors.Default;
}

First, a check is made if the form cache allows activation of forms (indicated by the Activation property of the form cache singleton instance). If the form cache does not allow activation of forms, the event handler is aborted.

Second, a check is made if this form now acts as a parent form; if so, this actually means that the form is returning from showing the child form and should keep its status (entered data, selected options, and so on). If not, this form is new, and because this form may have been used before (that is, it was retrieved from the form cache), all of the controls need to be reset to what they were when the form was first loaded. When implementing a form for the form cache, it's important to remember that the form may have any status when it is closed. The form status must be restored to its original state, as if it was opened for the first time, in the activation event.

Finally, because the form cache shows the wait cursor while new forms are processed, the default cursor must be restored at the end of the Activation event.

If a form uses more than one child form, the parentForm variable may be implemented as an enumeration like the following.

private enum parentFormType
{
    None,
    AnyChildForm,
    AnotherChildForm
}

Each child form is defined in the enumeration, and these values are used when the form is shown (or pushed).

parentForm = parentFormType.AnyChildForm;
AnyChildForm anyChildForm = (AnyChildForm)
    FormCache.Instance.Load(typeof(AnyChildForm));
anyChildForm.AnyAttribute = anyVariable;
FormCache.Instance.Push(typeof(AnyChildForm));

The Activation event looks something like the following.

private void AnyForm_Activated(object sender, EventArgs e)
{
    if(!FormCache.Instance.Activation)
        return;

    if(parentForm == parentFormType.None)
    {
        // Reset other controls
        // ...
    }
    else
    {
        switch(parentForm)
        {
            case parentFormType.AnyChildForm:
                // Code when coming back from AnyChildForm
                break;

            case parentFormType.AnotherChildForm:
                // Code when coming back from AnotherChildForm
                break;
        }
        parentForm = parentFormType.None;
    }
    Cursor.Current = Cursors.Default;
}

In this code, anything you need to do after the child forms completes (that is, is hidden), you can handle in one place.

The form's closing event also needs to be modified to adapt to the form cache.

private void AnyForm_Closing(object sender,
    System.ComponentModel.CancelEventArgs e)
{
    FormCache.Instance.Pop(1);
    e.Cancel = true;
}

The event handler includes a call to pop the previous (parent) form from the form stack, and it will never allow the form to close because the form cache handles closing (disposing) forms. This is also a good place to handle control focus because you want to make sure that the correct control has focus when you open the form again.

When the application closes, the cleanup of the cache finishes with the following line of code.

FormCache.Instance.DisposeAllForms();

This line is normally placed in the Closing event of the application's main form.

GlobalHandler

You can use the GlobalHandler class to translate complete forms, including all controls and menus. It only requires the following single line of code in each form.

GlobalHandler.Translate.Form(this);

Even simple texts, such as error messages, can be translated by using the following format.

MessageBox.Show(GlobalHandler.Translate.Text("MsgCantOpenWebPage",
    "Could not open web page!"), this.Text);

For more details about the management and code related to globalization, please see the article Northwind Pocket Sales: Field Sales for Windows Mobile-based Pocket PCs.

Conclusion

The concept of compression that comes with a solution based on Windows Mobile 2003 software for Smartphones and .NET Compact Framework can really compress the way you complete your business and the way you implement the technology support for that business. You can achieve your business goals faster and with lower cost or higher profit. By providing a complete sample, this article can get you quickly up to speed faster about implementing such solutions for your company or your customers.