CONTENT
 PRODUCTS
 ARTICLES
 DOWNLOAD
 SOURCE CODE
 BOOK
 ABOUT
 HOME

MVP

ADS




Efficient List and Detail Forms in .NET CF
We'll look at how to work with lists that can expand and collapse items, that can contain text, images and links, and that has great database support. We will also look at input forms that can hold many fields in various formats, and still be very efficient.

User interface design is always a key factor if an enterprise application is going to be a success. This is even more true for mobile enterprise applications mainly due to the fact that the user is probably somewhere out of the office and very often in a hurry just to get things done. Therefore, data entry must be very easy, fast, and generally straightforward.

Get the sample source code!

ListDetail Sample
ListDetail Sample
AdvancedList Control
If you are looking for a more efficient replacement for the standard ListView and DataGrid controls then the AdvancedList for .NET CF control from Resco could be your choice. The most interesting features are the possibility to use templates, the built-in data binding, and the powerful graphic abilities. Templates allows you to show the same data in different ways. You can at least have one template when the item is collapsed and another one when the item is expanded.

DetailView Control
The DetailView for .NET CF control from Resco can really change the way you provide data input for your users. If you decide to go with this control, you can say goodbye to most of the time you spend on designing forms. No more of that nudging of controls back and forth to make the form look good. If you like the way data is entered in the standard applications Contacts, Calendar and Tasks, this control is for you. It supports the most important input controls: TextBox, ComboBox, CheckBox, and DateTimePicker.

ListDetail Anyplace Sample
Building on the samples that come with the controls, the sample ListDetail Anyplace shows the use of both controls. The sample application is built for .NET CF with Visual Studio .NET 2003 and starts up with the following form:

AdvancedList control
AdvancedList control

As you can see in the figure, the AdvancedList control is used in a "find customers" form. Each item has two views (or templates), one collapsed and one expanded. When an item is collapsed, the name, address, country and even the country flag of the customer is shown. When an item is tapped, and therefore expanded, the customer's phone and fax numbers are shown as well as links to e-mail and web site. If the e-mail is tapped, a new e-mail is created to that customer, and if the web site is tapped, the browser is opened with that web page loaded. Finally, if the customer name is tapped, another form is opened that looks like this:

DetailView control
DetailView control

The form in the figure shows how the DetailView control can be used to edit detail information. In this case it is the customer's orders and even the order details. The "Order ID" pull-down list includes the orders for the customer, and when selection of order changes, the control data is reloaded from the database. Similarly, when the "Order Detail" selection is changed the detail information (product, price, etc) is updated.

The control support different field types (text, combobox, checkbox, and date/time), and extensive events make sure you can handle things like changing the color of the currently edited field as well as showing the SIP (soft keyboard) when in edit mode:

DetailView editing
DetailView editing

In the figure, please note also that since the DetailView control is scrollable the space beneath the SIP is not lost, and we can utilize the whole form for data. Another advantage with this control is the fact that a Pocket PC user is familiar with this way of entering data as it is very similar to the data input used in the standard applications Contacts, Calendar, and Tasks.

Let's see how to use these controls by having a closer look at the sample code.

Code Walkthrough
Much of the effort when using the AdvancedList control is done in the designer rather than in code. The Templates collection designer let you create numerous row templates (views) of the data. Each row template includes a collection of cell templates that is actually the cells (fields) to show for each item. Since the location of each cell is very hard to predict without the visual support, a suggestion is to first draw the cell layout on the form with Label (for TextCell and LinkCell) and PictureBox (for ImageCell) controls. Then, move the designer generated code for the labels to the text cells in the AdvancedList. This is the code generated by the designer for the text cells in the sample's first template (collapsed):
  textCell1.Bounds = new System.Drawing.Rectangle(0, 0, 180, 16);
  textCell1.CellSource.ColumnName = "CompanyName";
  textCell2.Bounds = new System.Drawing.Rectangle(0, 16, 110, 16);
  textCell2.CellSource.ColumnName = "Address";
  textCell3.Bounds = new System.Drawing.Rectangle(110, 16, 50, 16);
  textCell3.CellSource.ColumnName = "PostalCode";
  textCell4.Bounds = new System.Drawing.Rectangle(160, 16, -1, 16);
  textCell4.CellSource.ColumnName = "City";
The observant notes an important implementation detail: the designer is implemented to set the Bounds property and not a combination of the Location and Size properties. This is more efficient and even a recommendation for increasing form load performance. Therefore, you need to combine the Location and Size properties of the labels into one Bounds property (see the article Improving Microsoft .NET Compact Framework-based Application Form Load Performance for more details).

Each cell can be bound to a column (name, index, or constant) and an image cell can also be bound to a ImageList control that contain the images to show. This is the code generated by the designer for the first template's image cell that holds the image of the flag:
  imageCell1.Alignment = Resco.Controls.Alignment.TopRight;
  imageCell1.Bounds = new System.Drawing.Rectangle(200, 0, -1, 16);
  imageCell1.CellSource.ColumnName = "Flag";
  imageCell1.ImageList = imlFlags;
To make it all work, extra columns are added to the SQL query used for data binding:
  SELECT *, 0 AS Flag, 0 AS Background FROM Customers
Then, a hash table is created to hold the index of images for each flag and region (controls the background) and it is used to set the bound fields in the ValidateData event:
  string countryname = (string) e.DataRow["Country"];
  if (countries.Contains(countryname))
  {
    Country c = (Country) countries[countryname];
    e.DataRow["Flag"] = c.FlagId;
    e.DataRow["Background"] = c.RegionId;
  }
  else
    e.Skip = true;
When the user tap on a LinkCell is handled by the LinkClick event:
  if (e.DataRow.CurrentTemplateIndex == 1)
  {
    switch (e.CellIndex)
    {
      case 1: // Customer Name
        Cursor.Current = Cursors.WaitCursor;
        DetailForm detailForm = new DetailForm(this, 
          e.DataRow["CustomerID"].ToString());
        detailForm.ShowDialog();
        break;

      case 8: // E-mail
        RunApplication("iexplore", e.Target);
        break;

      case 10: // Web address
        RunApplication("iexplore", e.Target);
  	break;
    }
  }
A check is made that this is the second template (only one that contains link cells), and if the e-mail or web site cells are tapped, the browser is started (in the case of the e-mail address, the link is prefixed by "mailto:" in the UrlFormatString property).

If the customer name is tapped the details (order) form is displayed, and the same applies to the DetailView control used in that form – most of the work is done in the designer. This control, however, does not support data binding but the data is easily loaded using code like this (for loading the Products ComboBox):
  cmd.CommandText = @"SELECT ProductID, ProductName FROM products ORDER
                      BY CategoryID, ProductName";
  rdr = cmd.ExecuteReader();
  while (rdr.Read())
  {
    Product prod = new Product();
    prod.id = rdr.GetInt32(0);
    prod.Name = rdr.GetString(1);
    (Resco.Controls.ItemComboBox)dvwOrder.Items["Product"]).Add(prod);
  }
Product is a helper class to hold the data for the Products ComboBox, and the ComboBox item is loaded much like any normal ComboBox.

When the user selects a new order or order detail, the reload of data is handled in the ItemChanged event:
  // If event was thrown by OrderID, reload data
  if (e.Name == "OrderID")
  {
    LoadOrderData();
    return;
  }

  // If event was thrown by OrderDetail, change OrderDetail
  if (e.Name == "OrderDetail")
  {
    // Set product
    foreach(Product prod in ((Resco.Controls.ItemComboBox)
      dvwOrder.Items["Product"]).Items)
    {
      if (prod.id == ((OrderDetail)
        dvwOrder.Items["OrderDetail"].Value).ProductID)
      {
        ((Resco.Controls.ItemComboBox)
          dvwOrder.Items["Product"]).SelectedItem = prod;
        break;
      }
    }
    // Set all other parameters
    dvwOrder.Items["UnitPrice"].Text = ((OrderDetail)
      dvwOrder.Items["OrderDetail"].Value).UnitPrice.ToString();
    dvwOrder.Items["Quantity"].Text = ((OrderDetail)
      dvwOrder.Items["OrderDetail"].Value).Quantity.ToString();
    dvwOrder.Items["Discount"].Text = ((OrderDetail)
      dvwOrder.Items["OrderDetail"].Value).Discount.ToString();
    dvwOrder.Refresh();
    return;
  }

  // If order detail was changed, update also order detail object
  if (e.item.Name == "Product")
    ((OrderDetail)dvwOrder.Items["OrderDetail"].Value).ProductID =
      ((Product)((Resco.Controls.ItemComboBox)e.item).Value).id;
  if (e.item.Name == "UnitPrice")
    ((OrderDetail)dvwOrder.Items["OrderDetail"].Value).UnitPrice =
      Convert.ToInt32(e.item.Text);
  if (e.item.Name == "Quantity")
    ((OrderDetail)dvwOrder.Items["OrderDetail"].Value).Quantity =
      Convert.ToInt32(e.item.Text);
  if (e.item.Name == "Discount")
    ((OrderDetail)dvwOrder.Items["OrderDetail"].Value).Discount =
      Convert.ToDouble(e.item.Text);
The private LoadOrderData method simply loads the order and order details data from the database. The order details data is loaded in a helper class (OrderDetail) that is also updated when the fields are updated.

The events fired when an item gets or looses the focus are very useful for things like highlighting the edited field or showing the SIP. Here is the code for the ItemGotFocus event:
  rchInk.LoadFile("filename.pwi", RichInkStreamType.RichInk);
To implement the Edit menu, you simply need to respond to the menu options with the supported methods:
  if (e.item != null)
  {
    colLabel = e.item.LabelBackColor;
    e.item.LabelBackColor = Color.LightBlue;
    if (e.item is Resco.Controls.ItemTextBox)
      inpSIP.Enabled = true;
    dvwOrder.Refresh();
  }
It saves the background color of the item label (in colLabel) and changes it to light blue instead. This way, the user gets visual feedback on selecting a field for editing. Also, if the field is a TextBox item, the SIP is enabled (shown). Consequently, this is the code for the ItemLostFocus event:
  if (e.item != null)
  {
    e.item.LabelBackColor = colLabel;
    if (e.item is Resco.Controls.ItemTextBox)
      inpSIP.Enabled = false;
    dvwOrder.Refresh();
  }
For more complete examples, see the ListDetail Anyplace source code and the samples that comes with the controls.

Conclusion
The challenge to create a more efficient user interface is greatly reduced by these controls. Both controls are very powerful for their purpose and the combination of the two is even more impressing. The advice is clearly to download the evaluation to take a closer look at this article's sample and also the samples that comes with the controls.

Any comments?


©2001-2009 Christian Forsberg & Andreas Sjöström