Writing Mobile Code Essential Software Engineering for Building Mobile Applications [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Writing Mobile Code Essential Software Engineering for Building Mobile Applications [Electronic resources] - نسخه متنی

Ivo Salmre

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید











Performance Design Strategies for User Interface Code


User interface code is complex because it is the boundary where your code interacts with both the operating system and the end user. Your user interface literally sits in between and brokers requests connecting the user and the operating system. As an application designer, you face myriad choices:

Which standard or custom controls should be used to display data and interact with the user?

Does it make sense to build your own custom controls or extend the behavior of existing controls to meet special needs?

What strategies should be used for populating user interface elements that hold a lot of data? Controls containing lists or trees of data are of particular importance.

Which control events should be responded to? When should this response be suppressed?

Is there a way to reduce the number of events that get fired when common or repetitive tasks occur?

Is it necessary to force the immediate update or repainting of a control for short-term user experience gain at the cost of longer-term performance?


Because of all these dimensions of choices, the possible code implementations to solve a specific user interface need are almost endless. This is liberating but at the same time it also allows the possibility of haphazard and inefficient design. It is as if you went to the furniture store to buy a new couch and they handed you several bags of parts, more than you need for any single couch, and gave you no instructions but instead sent you off to create your own couch. Maybe you end up with a great-looking and sturdy couch, or maybe you end up with something less structurally sound that resembles the primitive shelter building efforts of early man. Without instructions, a strategy and good construction guidelines, odds are you will end up with the latter. With a good design plan, you will get the former. As with our fictitious couch, after you put enough pieces together, it becomes hard to take them all apart again and revise your design. If you want to avoid a substandard result or the need to start again from scratch, you need some design strategies to guide you and ensure your application's user interface development stays on the right path.

Below are covered some of the main design strategies for performance-oriented user interface design.

Use the Built-In Performance Features


If the mobile application runtime you are using has specific features to help build well-performing user interface code, you should use them. Moreover, you should actively review the list of events, properties, and methods for the controls you use and look for specific performance-oriented features. This seems obvious but is very often overlooked. The best way to learn about the performance-oriented features of a user interface framework is to examine members of its window, form, or control classes. It is also useful to examine the overloaded versions of methods that exist to perform common tasks to see whether there are methods that enable you to perform operations in a batched mode rather than by calling a method over and over again in a loop. As you would expect, batched mode operations are typically faster. This review usually only takes a few minutes, and you may be surprised at what you discover; lots of poor interface code is simply the result of using inefficient properties or methods to get things done or not calling methods that suspend user interface actions when updates are being performed.


.NET Compact FrameworkPerformance Using TreeViews and ListViews


The TreeView and ListView controls are both useful for displaying sets of related data in user interfaces. Because these controls work with sets, there is often a need to add or remove items in bulk from the controls. To achieve this efficiently, both controls offer methods to improve performance.

.BeginUpdate() / .EndUpdate()
Both of these methods exist on the TreeView and ListView controls. They serve to suspend and resume the redrawing of a control. Calling BeginUpdate() tells the control not to redraw automatically each time an item is added or removed. EndUpdate() tells the control that it can update its display again. Unnecessary drawing can be very taxing to the performance of your application. If your application is adding or removing multiple items from controls, it is a good idea to surround the code that adds or removes the items with calls to BeginUpdate() and EndUpdate().

.AddRange()
The TreeView control's nodes collection contains an AddRange() method (for example, treeView1.Nodes.AddRange()) that enables you to bulk add a set of tree nodes into the TreeView control. Using this batch mode is preferable to iteratively adding each node.


The performance and user interface smoothness benefits of using these built-in efficiency mechanisms can be substantial. When doing a lot of work with any control, it is a good idea to scan its list of properties and methods to see whether any specific members exist to aid in improving performance.

Example: Performance Differences in Varying Approaches for Working with a TreeView Control


This code in Listing 11.1 measures the efficiency of populating a .NET Compact Framework TreeView control using three different techniques. To build the sample, use Visual Studio .NET to create a new C# mobile device project and select the Pocket PC as your target platform. Onto the blank Form designer, add a TreeView control and five buttons as shown in Figure 11.1.

Figure 11.1. The Visual Studio .NET Form Designer with TreeView and Button controls.

[View full size image]


Visual Studio .NET Will Add and Wire Up a Blank Event Handler for You


All you need to do is double-click the button in the Visual Studio .NET Form designer. The name of the function added will be the name of the control (for example, button1) + _Click. Visual Studio will (1) create the event handler function for you, (2) write code into the Form's InitializeComponent() function to hook up the click event handler it just created, and (3) bring you into the code editor to write the code that goes into the event handler. If you want to change the name of the button, do so by changing the Name property in the Properties window (the right-side window in Figure 11.1). It is helpful to do this before you double-click the button to create and wire up the event handler because when the event handler function is created it uses the current control name. If the control name is changed after the event handler function is created, the event handler will still be hooked up properly, but its name will not match the new name of the control. Fixing this so the control name and event function name match will require manual steps by you; it's not hard, but it is extra work.

The code in Listing 11.1 consists of a set of click event handlers for the various buttons on your form. The actual function names you use will be based on the names you give your button controls. I have the used the following button control names in my code: UnOptimizedFill, UnOptimizedClear, UseBeginEndUpdateForFill, UseBeginEndUpdateForClear, FillArrayBeforeAttachingToTree. If you use the default button names Visual Studio .NET gives, you will have button1, button2, button3, button4, and button5 rather than the event handler function names that will change accordingly. In either case, it is easiest to double-click each of the buttons in the Visual Studio .NET Form designer to create and hook up the empty event handlers and then enter the event handling code listed inside the function definitions created for you.

Listing 11.1. Populating and Clearing a TreeView Control Using Alternative Strategies



//------------------------------------------------------------
//Note #1: This sample uses the "PerformanceSampling" class
// defined earlier in this book. Make sure this class
// is included in your project.
//Note #2: This code need to be inserted into a Form class that
// has a TreeView control and buttons hooked up to the
// xxx_Click functions below.
//------------------------------------------------------------
//Number of items to place into the tree view
const int NUMBER_ITEMS = 800;
//--------------------------------------------
//Code for: "Fill: Baseline" Button
//
//"Unoptimized" Approach to filling a TreeView
//--------------------------------------------
private void UnOptimizedFill_Click(
object sender, System.EventArgs e)
{
//To make sure we're testing the same thing, make sure
//the array is clear
if (treeView1.Nodes.Count > 0)
{
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//For more consistent measurement, Collect the garbage
//before running
System.GC.Collect();
//Start the test timer
PerformanceSampling.StartSample(0, "TreeViewPopulate");
//Fill the TreeView
for(int i = 0; i < NUMBER_ITEMS ; i++)
{
treeView1.Nodes.Add("TreeItem" + i.ToString());
}
//Stop the test timer & and show the results
PerformanceSampling.StopSample(0);
System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(0));
}
//--------------------------------------------
//Code for: "Clear: Baseline" Button
//
//"Unoptimized" Approach to filling a TreeView
//--------------------------------------------
private void UnOptimizedClear_Click(
object sender, System.EventArgs e)
{
//For more consistent measurement, Collect the garbage
// before running
System.GC.Collect();
//Start the test timer
PerformanceSampling.StartSample(1, "TreeViewClear");
treeView1.Nodes.Clear();
PerformanceSampling.StopSample(1);
System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(1));
}
//--------------------------------------------
//Code for: "Fill: BeginUpdate" Button
//
//"Using BeginUpdate()" Approach
//--------------------------------------------
private void UseBeginEndUpdateForFill_Click(
object sender, System.EventArgs e)
{
//To make sure we're testing the same thing, make sure
//the array is clear
if (treeView1.Nodes.Count > 0)
{
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//For more consistent measurement, Collect the garbage
// before running
System.GC.Collect();
//Start the test timer
PerformanceSampling.StartSample(2,
"Populate - Use BeginUpdate");
//Fill the TreeView
treeView1.BeginUpdate();
for(int i = 0; i < NUMBER_ITEMS ; i++)
{
treeView1.Nodes.Add("TreeItem" + i.ToString());
}
treeView1.EndUpdate();
//Stop the test timer & and show the results
PerformanceSampling.StopSample(2);
System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(2));
}
//--------------------------------------------
//Code for: "Clear: BeginUpdate" Button
//
//"Using BeginUpdate()" Approach
//--------------------------------------------
private void UseBeginEndUpdateForClear_Click(
object sender, System.EventArgs e)
{
//For more consistent measurement, Collect the garbage
//before running
System.GC.Collect();
//Start the test timer
PerformanceSampling.StartSample(3, "Clear - Use BeginUpdate");
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
//Stop the test timer & and show the results
PerformanceSampling.StopSample(3);
System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(3));
}
//--------------------------------------------
//Code for: "Fill: Use Array" Button
//
//"Using Array" Approach
//--------------------------------------------
private void FillArrayBeforeAttachingToTree_Click(
object sender, System.EventArgs e)
{
//To make sure we're testing the same thing, make sure
// the array is clear
if (treeView1.Nodes.Count > 0)
{
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
treeView1.EndUpdate();
treeView1.Update();
}
//For more consistent measurement, Collect the garbage before
// running
System.GC.Collect();
//Start the test timer
PerformanceSampling.StartSample(4, "Populate - Use Array");
//Allocate space for our array of tree nodes
System.Windows.Forms.TreeNode [] newTreeNodes =
new System.Windows.Forms.TreeNode[NUMBER_ITEMS];
//Fill up the array
for(int i = 0; i < NUMBER_ITEMS ; i++)
{
newTreeNodes[i] =
new System.Windows.Forms.TreeNode(
"TreeItem" + i.ToString());
}
//Connect the array to the TreeView
treeView1.BeginUpdate();
treeView1.Nodes.AddRange(newTreeNodes);
treeView1.EndUpdate();
//Stop the test timer and show the results
PerformanceSampling.StopSample(4);
System.Windows.Forms.MessageBox.Show(
PerformanceSampling.GetSampleDurationText(4));
}

The results of various approaches to adding and removing items from the TreeView control are listed in Table 11.1 and Table 11.2.

Table 11.1. Physical Pocket PCAdding 800 Items (Results in Seconds)

Trial #

Unoptimized

Using BeginUpdate()

Using Array

1

40.785

12.484

10.388

2

40.533

12.322

10.419

3

40.878

13.343

11.686

Average

40.732

12.716

10.831

% Time savings vs. baseline test

Baseline (0%)

68.78%

73.41%

Table 11.2. Clearing 800 Items (Results in Seconds)

Trial #

Unoptimized

Using BeginUpdate()

1

18.791

8.656

2

15.91

8.964

3

16.821

8.516

Average

17.174

8.712

% Time savings vs. baseline test

Baseline (0%)

49.27%

As can be seen from the numbers in Table 11.1, using the BeginUpdate() and EndUpdate() methods surrounding the code that adds items to the TreeView control resulted in a time savings of about two thirds (68.78 percent). Additionally, the visual experience was more appealing because the end user suffers less control redraw flicker during the update. Using the AddRange() method ("Using Array" column) to populate the TreeView saved the application an additional 6 percent of overhead over using BeginUpdate()/EndUpdate(); this is also notable.

It was surprising to discover that not only did BeginUpdate()/EndUpdate() greatly increase the speed of adding items to the TreeView control, it also had a large impact on the speed of removing items. Table 11.2 compares the different approaches for removing items from the TreeView.

As can be seen from Table 11.2, a time savings of almost 50 percent was achieved by just surrounding the code that removes items from the TreeView with calls to BeginUpdate() and EndUpdate().

The lessons learned from these measurements are threefold:

It is important to seek out the built-in performance improving mechanisms present in the user interface framework you are using.

It is wrong to assume that conceptually simple operations such as "clear all the items out of the control" will automatically run quickly and cannot be speeded up.

It is always worth taking the time to measure the speed of various approaches to find the most optimum way to accomplish a user interface task.

Test with the Actual Number of Elements You Will Display in Your Application


A common design mistake encountered when writing user interface code is to design and test with smaller sets of data than will actually be encountered in the mobile application's deployment. An algorithm that works fine retrieving 20 items from a database and populating a user interface with them may or may not work well with 200 items. If you are using smaller sets of data for your testing, you are leaving the door open for nasty surprises later in the development or deployment cycle when the real-world data comes in. It is far better to discover these kinds of performance issues earlier when creative design strategies can be explored for mitigating them; late design changes will always be harder to implement, riskier to application stability, and usually produce less-satisfying results.

Another common mistake is using different data sources during the design of an application. For example, using a local database or text file during design as stop gap until the real data becomes available on the remote server the mobile application will actually have to access. Most often these kinds of issues occur either when the real databases and data are not yet available for use at application design time or when the data is sensitive and live data is not available to the application designers for testing purposes. Similar kinds of performance shocks can occur when data sources are switched. As above it is best test early with real-world conditions.

In any case, it is important to specify the capacity, location, and access mechanism for the data your application will display in its user interface. Do this early in the design process and test with data of this size, location, and access mechanism. If your application is intended to work with a large variety of data sizes, test with the largest capacity. It is highly recommended to give yourself some performance buffer and test with data capacities that exceed your design specification by a reasonable amount and see how the application's performance is affected. If your data can be pulled from different sources, test the performance using each data-access mechanism. If your application will need to access the data via a dialup or lower-bandwidth connection, test it under these conditions. Not only should the application run correctly in these situations, it is also important that your application remain visually attractive and behave responsively throughout any prolonged user interface update. Doing these things will keep your design process focused on the real-world performance you will need to achieve. There simply is not a substitute for working with same data in the same way that your end users will.

Procrastination Is Good! Defer, Defer, Defer...


Many applications serve to give the user a condensed view onto a large amount of dynamic data. The data may be stored locally in a file or database, or pulled dynamically down from a network as needed.

For example, a mobile application for real estate agents may give a view onto hundreds or thousands of properties and organize them in a hierarchy navigable by the application's user via a mobile phone or a PDA. A first-iteration design to achieve this goal might typically load all the skeletal information about available house listings into memory and enable the user to navigate the hierarchy and find potential properties. When a specific property is selected the application would then load details such as photos, floor plans, street maps, and the market status for the property on demand.

For the top-down navigation of housing choices, a TreeView control can offer rich hierarchical view on the possible property listings. For instance, a TreeView control could be used to offer three top-level nodes, each representing a different pivot of the data based on a probable decision process that an end user might use to navigate the data. Below are three examples of listing hierarchies that the application's users might want:

Navigation 1
Start with the neighborhood, and then look at price groupings to determine which units to show. TreeView hierarchy: Neighborhoods->Price->ListOfUnits.

Navigation 2
Start with the price groupings, and then look at house types and finally the neighborhood to arrive at the list of units to show. TreeView hierarchy: Price->HouseType-> Neighborhood->ListOfUnits.

Navigation 3:
Start with the type of house, and then look at price ranges and then the neighborhoods. TreeView hierarchy: HouseType->Price->Neighborhood->ListOfUnits.


Figure 11.2 shows what this TreeView navigation might look like. Three top-level nodes Neighborhood, Price, and HouseType exist. Each parent node has subnodes enabling users to drill down through the hierarchy and finally arrive at the set of houses they want to look at.

Figure 11.2. A simple example of what TreeView real estate navigation could look like.

Although a tree is a potentially useful metaphor for enabling house selection based on hierarchical categories, there is a potential problem; the tree can grow quite large even for a moderate amount of data. A simple hierarchy of 10 neighborhoods * 4 house types in each * 4 price groupings in each * 6 houses meeting each criteria = 960 leaf nodes in our tree. This number would be further multiplied by the number of top-level navigations we want to support.

960 for Neighborhoods->House Type ->Price ->ListOfUnits

960 for Price->Neighborhoods->HouseType->ListOfUnits

960 for HouseType->Price->Neighborhood->ListOfUnits


If we populate the TreeView in advance of the application user's navigation, we will end up creating and populating a huge number of tree nodes, most of which users will never navigate to in any given application usage session. Creating all these TreeView members takes time, and the TreeView item nodes themselves consume system resources. Holding thousands of TreeView items in memory would have a detrimental effect on our mobile application's performance. Unless we think the user is going to visit each of these nodes individually and repeatedly, we can achieve better performance by using a more sophisticated approach. What we would like to do is keep the TreeView user interface metaphor but avoid creating hundreds or thousands of unvisited TreeView items. A way to accomplish this is to create the needed TreeView nodes only when it becomes clear that the user will access them. This can be done by using some clever code to (1) populate the TreeView control only up until the point where it has been expanded to, and (2) handle the event that indicates a node of the TreeView control is about to be expanded and needs to be populated with valid data. Doing this can save the mobile application the time and resources required to fill a huge amount of nodes and will result in a better end user experience.

More sophisticated approaches such as looking ahead one level deep in the TreeView and prepopulating the nodes are also possible; this could potentially provide the user an even richer experience. The richness of the approach you choose to use to intelligently defer work is bounded only by the work required to design and implement your concepts.


.NET Compact FrameworkNot All Supported Event Handlers Are Exposed by the Visual Studio .NET Designer


The .NET Compact Framework supports a subset of the desktop .NET Framework's control events. It is worth noting that just because an event signature is defined in the .NET Compact Framework is no guarantee that the event is fired at runtime. There may have been specific compatibility and object inheritance reasons that the event definition for a control needed to be included but is not triggered by the .NET Compact Framework at runtime. The proof is wiring the event up and seeing whether it fires at runtime. Further complicating this situation is the fact that not all of the events supported by the .NET Compact Framework are exposed by the Visual Studio .NET graphical design environment. There are supported events that are not listed in the C# events list in the property sheet or the Visual Basic .NET event drop-down box in the code editor.

An event that is exposed by the design environment should be supported by the .NET Compact Framework, but there are additional events that are supported but not exposed by Visual Studio .NET. The reason for this is project coordination: the design time team and the runtime team were not perfectly in sync! (Expect this to get better with future iterations.) The most common events are exposed but some more specialized events may require you to hook them up by hand. This is not hard to do, but it requires a little bit of specialized knowledge about how event handlers are hooked up.

If you want to use an event that is supported by the .NET Compact Framework but not exposed by the Visual Studio .NET design environment, you will need to manually insert one line of code into the InitializeComponent() function of the form containing the control. You will find the InitializeComponent() function in the normally hidden or collapsed "Windows Form DesignerGenerated Code" section in the class' code editor.

The code snippet shows how to add a BeforeExpand event handler for a TreeView control:


#region Windows Form Designer generated code
private void InitializeComponent()
{
....a bunch of code for other controls...
//
// treeView1
//
this.treeView1.ImageIndex = -1;
this.treeView1.Location = new System.Drawing.Point(72, 48);
this.treeView1.Name = "treeView1";
this.treeView1.SelectedImageIndex = -1;
this.treeView1.Size = new System.Drawing.Size(168, 176);
this.treeView1.TabIndex = 0;
//HERE IS THE 1 LINE OF CODE YOU NEED TO WRITE TO HOOK UP THE EVENT
this.treeView1.BeforeExpand += new System.Windows.Forms.
TreeViewCancelEventHandler(this.TreeView1BeforeExpand);
... a bunch of code for other controls
}

The code above hooks up an event handler for the BeforeExpand event of the treeView1 control. The event handler must have a specific function signature. In this case, it is as follows:


private void TreeView1BeforeExpand(object sender,
System.Windows.Forms.TreeViewCancelEventArgs e)
{
}

A good way to have both of these code snippets autogenerated for you is to use a desktop Windows Application project. The Visual Studio .NET desktop projects support graphically creating and hooking up all supported event handlers. This code can be copy/pasted into the appropriate parts of your .NET Compact Framework application.

A slightly modified variant of the strategy above is also worth considering. Because the form designer automatically inserts and deletes code inside the InitializeComponent() function, there is a chance it could step on top of your custom-added code. To avoid this, you may want to create your own function (for example, MyInitializeComponent()) to place your custom initialization code into and call this function in the form's constructor code right after the call to InitializeComponent(). Doing this will ensure that the form's designer does not accidentally delete your code.

Example: On-Demand Population of a TreeView Control


Figure 11.3 shows a simple application consisting of a TreeView control (treeview1) and a Button (button1). Clicking the button at runtime sets up or resets the state of the TreeView control. After the TreeView control has been set up by clicking the button, it offers three top-level nodes that can be dynamically populated. The nodes are Neighborhoods, Prices and HouseTypes. To keep the code size small and the sample simple, only the code to dynamically populate the Neighborhoods node is included in the Listing 11.2. Clicking the other nodes will bring up a MessageBox the first time they are clicked to show where the dynamic TreeView population should occur and you are free to add your own code here.

Figure 11.3. Pocket PC emulator with an application that dynamically populates a TreeView control.

Listing 11.2 contains the code that needs to be inserted into a Form class for this example. To create the sample, follow these steps:


1.

Start a new Smart Device project in Visual Studio .NET and select the Pocket PC as the target platform.

2.

Add a TreeView control and a Button control to the Form designer.

3.

Double-click the Button in the Form designer; this will create and wire up the button1_Click event handler below.

4.

Enter the Button1_Click code listed below to populate the TreeView.

5.

Enter the rest of the code listed below including the constants defined above the button1_Click event handler.

6.

Hand wire in the event handler for the TreeView's BeforeExpand event, as described in the section above.

7.

Compile and run the example.


Listing 11.2. Dynamic Population of TreeView Control



//Dummy text to put in the placeholder child nodes
const string dummy_node = "_dummynode";
//Tag we will use to indicate a node
const string node_needToBePopulated = "_populateMe";
//Text we will use for our top-level nodes
const string nodeText_Neighborhoods = "Neighborhoods";
const string nodeText_Prices = "Prices";
const string nodeText_HouseType = "HouseTypes";
//-------------------------------------------------------------
//Click event handler for our button
//
//Sets up our TreeView to show incremental filling of the
//tree
//-------------------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)
{
TreeNode tnNewNode;
//Turn off UI updates before we fill in the tree
treeView1.BeginUpdate();
//Throw out any old data
treeView1.Nodes.Clear();
//-----------------------------
//"Neighborhoods" node
//-----------------------------
//Add the top-level "Neighborhoods" node.
tnNewNode = treeView1.Nodes.Add("Neighborhoods");
//Set a tag on the node that indicates that we will
//dynamically fill in the node
tnNewNode.Tag = node_needToBePopulated ;
//This dummy child node only exists so that the node has
//at least one child node and therefore the tree node is
//expandable.
tnNewNode.Nodes.Add(dummy_node);
//-----------------------------
//"Price" node
//-----------------------------
tnNewNode = treeView1.Nodes.Add("Price");
//Set a tag on the node that indicates that we will
//dynamically fill in the node
tnNewNode.Tag = node_needToBePopulated ;
//This dummy child node only exists so that the node has
//at least one child node and therefore the tree node is
//expandable.
tnNewNode.Nodes.Add(dummy_node);
//-----------------------------
//"HouseType" node
//-----------------------------
tnNewNode = treeView1.Nodes.Add("HouseType");
//Set a tag on the node that indicates that we will
//dynamically fill in the node
tnNewNode.Tag = node_needToBePopulated ;
//This dummy child node only exists so that the node has
//at least one child node and therefore the tree node is
//expandable.
tnNewNode.Nodes.Add(dummy_node );
//Resume the UI updates
treeView1.EndUpdate();
}
//------------------------------------------------------
//BeforeExpand event handler for our TreeView
//NOTE: This event handler will have to be hooked up
// by hand in the Form's "InitializeComponent()"
// function.
//
//Called when a user asks to expand a node that has at least
//one child node. This is called before the node's children
//are shown and gives us a chance to dynamically populate the
//TreeView control.
//------------------------------------------------------
private void TreeView1BeforeExpand
(object sender, System.Windows.Forms.TreeViewCancelEventArgs e)
{
//Get the node that is about to be expanded
System.Windows.Forms.TreeNode tnExpanding;
tnExpanding = e.Node;
//If the node is not marked "need to be populated" the
//node is fine "as is."
if(tnExpanding.Tag != (object) node_needToBePopulated)
{
return; //Allow things to continue without hindrance
}
//--------------------------------------------------------
//Dynamic tree population required.
//We know the node needs to be populated, figure out which
//node it is
//--------------------------------------------------------
if(tnExpanding.Text == nodeText_Neighborhoods)
{
PopulateTreeViewNeighborhoods(tnExpanding);
return; //done adding items!
}
else
{
//Check other possibilities for tree nodes we need to add.
System.Windows.Forms.MessageBox.Show(
"UN-DONE: Add code to dynamically populate this node");
//Remove the tag from the node so we don't run this
//code again
tnExpanding.Tag = ";
}
}
//------------------------------------------------------
//This function is called to dynamically add child nodes
//To the "Neighborhood" Node
//------------------------------------------------------
void PopulateTreeViewNeighborhoods(TreeNode tnAddTo)
{
TreeView tvControl;
tvControl = tnAddTo.TreeView;
tvControl.BeginUpdate();
//Clear the dummy subnode we have in there
tnAddTo.Nodes.Clear();
//Declare four nodes we want to make children
//of the node that was passed in.
TreeNode [] newNeighborhoodNodes;
newNeighborhoodNodes = new TreeNode[4];
newNeighborhoodNodes[0] = new TreeNode("Capitol Hill");
newNeighborhoodNodes[1] = new TreeNode("Chelsea");
newNeighborhoodNodes[2] = new TreeNode("Downtown");
newNeighborhoodNodes[3] = new TreeNode("South Bay");
//Add the child nodes to the tree view
tnAddTo.Nodes.AddRange(newNeighborhoodNodes);
tvControl.EndUpdate();
}

The code above shows how, with some cleverness and only a bit of additional code, complex user interface population can be deferred until the user has a need for the data. Whether working with the .NET Compact Framework or other device runtimes, learning how to defer the population of expansive user interface elements is a powerful technique.

Keep a Careful Eye on Event-Driven Code


A large portion of modern user interface code consists of code that responds to framework-generated events. Knowing which events to handle, how often the events are triggered, and when to avoid responding to events is important for designing user interface code that performs well.

A common situation that causes poor application performance is mistakenly handling events generated by code believing that the events are user generated.

Example: Showing the TextBox "Changed" Event Being Triggered When the .Text Property Is Set


Listing 11.3 contains the code that needs to be inserted into a Form class for this example. To create the sample, follow these steps:


1.

Start a new Smart Device project in Visual Studio .NET, selecting the Pocket PC as the target platform.

2.

Add a TextBox, Label, ListBox, and Button to the Form.

3.

Double-click the Button in the Form designer; this will create and wire up the button1_Click event handler below. Enter the code in Listing 11.3 to respond to this event.

4.

Double-click the TextBox in the Form designer; this will create and wire up the textbox1_ TextChanged event handler below. Enter the code in Listing 11.3 to respond to this event.

5.

Compile and run the example.

6.

Type text into the text box and note that each key press runs the textbox1_TextChanged() event code below.

7.

Click the Button and note that it also triggers the textbox1_TextChanged() event code below.


Note

Programmatically setting the text property of the TextBox actually triggers the TextChanged event twice when running on the .NET Compact Framework Version 1.1.

Running the same code on the desktop triggers the event only once. It is likely that in the future releases the .NET Compact Framework behavior will be changed to match the desktop .NET Framework behavior (only one event triggering). Events can be tricky stuff. Keep an eye on how and when events fire.

Listing 11.3. Programmatic TextBox Update Causes Event Code to Be Run



int m_eventTriggerCount;
private void textBox1_TextChanged(object sender, System.EventArgs e)
{
m_eventTriggerCount++;
//Update a label to show the # of events
label1.Text = "Events: #" + m_eventTriggerCount.ToString();
//List each of the events
listBox1.Items.Add(m_eventTriggerCount.ToString() + textBox1.Text);
}
private void button1_Click(object sender, System.EventArgs e)
{
//This triggers a TextChanged event
//same as if the user typed in text
textBox1.Text = "Hello World";
}

Listing 11.3 shows that programmatically setting the Text property of a TextBox fires the same event code as a user typing in text. Depending on your assumptions, this may or may not be what you expected. Often people write code to populate user interfaces after retrieving data from some external source. RadioButton and CheckBox buttons have their Checked properties set, TextBox values are filled, ListBox and ComboBox contents are filled, and so on. It is often assumed that this setup work does not trigger user interface events. Usually the intent is not to want these events to be triggered as the user interface is just being set up for the user to use. Very often the only time an application's programmer wants the application's event handler code to be run is when an external event occurs such as a timer ticking, a user clicking a button, or a user entering text into a control.

Avoiding Getting Caught Off-Guard by Events


Event code is tricky because it is not possible to tell by inspection when an application's event handling code will be called. Examining an application's source code shows clearly when calls are made to runtime libraries and to other application code, but does not indicate when the runtime triggers application events. For this reason, most developers simply make assumptions about when events are triggered and do not check these assumptions closely. In addition, as an application grows in user features and correspondingly in internal complexity more event handling and event generating user interface code gets written; this code may have subtle interactions that are not apparent by inspection. For example, code that responds to selecting an item in a ListBox may run code that causes an update to a CheckBox's and a TextBox's properties. For example:


textboxUserName.Text = selectedRecord.Username;
checkboxDeliverPackageToHomeAddress.Checked =
selectedRecord.DeliverToHome;

This code seems simple, but if events are hooked up to the TextBox or CheckBox controls they may be triggered and may run application code meant only to process end-user actions. This code may in turn change other user interface elements and cause a cascade of events unexpected by the application's developer.

Here are three good ways to help ensure that your application does not get caught off-guard with events:

Instrument application code during development.
Counting the number of times an event gets fired is a low-overhead activity; it only requires incrementing an integer. It can also be diagnostically useful to keep an ordered list of events that occur and examine these. This instrumentation can be conditionally compiled into your application. It is a good idea to periodically audit your application's behavior throughout the development cycle and examine what a highly instrumented version tells you. Instrumentation is easy and remarkably effective.

Have a clear state model for when events should run code and when they should immediately exit.
A common case where event code is unintentionally run is when the application's user interface is being populated with data. In these cases, the desire is simply to push data into the user interface and not to respond to events triggered by this push. An easy way to deal with this is to set a Form-level flag that states "the application is doing user interface updates now, event handlers should exit without processing." Each event handler should in its first line of code check the state of this flag and if it is set, exit without further processing.

Periodically place breakpoints in each of the applications event handlers and walk through the sequence of events that execute.
Periodically it is useful to single step through the set of events that fire in your user interface and see what processing is going on and in what order. You may find event cascades that are inadvertently being caused when the user interface is updated by application code; you may also find redundant processing you are doing that is costly. Event code that causes multiple redundant reads from or updates to a database is a good example of code that should be discovered and eliminated.

Event-driven code is a wonderfully useful concept, but like any abstraction it comes at a cost of reduced low-level understanding of what is going on. Unless you understand the sequence and frequency of events that are causing your application's code to run, you cannot truly say that you understand the behavior of your application. This can cause not only performance problems but also reliability problems. It is well worth spending the time to get a firm and detailed understanding of where and when the events in your application are firing.

Figure 11.4 and Listing 11.4 show a sample application that demonstrates both instrumentation and using a state model to prevent user interface event code from running when programmatic updates to the application's user interface are occurring. To create the sample, follow these steps:


1.

Start a new Smart Device project in Visual Studio .NET, selecting the Pocket PC as the target platform.

2.

Add a TextBox, RadioButton, ListBox, and Button control to the Form (see Figure 11.4 for how this should look).

3.

Double-click the Button1 in the Form designer. This will create and wire up the button1_Click event handler below. Enter the code below in Listing 11.4 to respond to this event.

4.

Double-click the TextBox in the Form designer. This will create and wire up the textbox1_TextChanged event handler below. Enter the code below in Listing 11.4 to respond to this event.

5.

Double-click the RadioButton1 in the Form designer. This will create and wire up the radioButton1_Click event handler below. Enter the code in Listing 11.4 to respond to this event.

6.

Rename the second button from button2 to buttonShowEventLog and double-click the Button in the Form designer. This will create and wire up the buttonShowEventLog_Click event handler below. Enter the code in Listing 11.4 to respond to this event.

7.

Enter the rest of the code below including the #if and #endif statements and the class-level variables.

8.

To the very top of the Form's class file, add the statement #define EVENTINSTRUMENTATION. This will enable the conditionally compiled code.

9.

Compile and run the example. Click Button1, type in the TextBox, and click the ShowEventLog button to see the list of instrumented events that have been triggered.

10.

Stop the application's execution and uncomment the line m_userInterfaceUpdateOccuring = true; in the Button1_Click event handler and then re-run the application. Observe that this flag, when set to true, had prevented the unwanted application code from running when the event handlers were triggered by programmatic access to the properties of the controls.


Listing 11.4. Using a State Model for Updates and Instrumentation to Better Understand and Control Event Processing



//-------------------------------------------------------------
//Place this #define statement at the top
//of the class if event logging is desired
//#define EVENTINSTRUMENTATION
//-------------------------------------------------------------
//-------------------------------------------------------------
//A flag that tells control event handlers if they should
//exit without doing any work
//-------------------------------------------------------------
bool m_userInterfaceUpdateOccuring;
//Counters for event occurrences
private int m_radioButton1ChangeEventCount;
private int m_textBox1ChangeEventCount;
//-------------------------------------------------------------
//Code we only want to include if we are running in an
//instrumented mode. This code has relatively high execution
//overhead and we only want to compile it in and run it if
//we are doing diagnostics.
//-------------------------------------------------------------
#if EVENTINSTRUMENTATION
private System.Collections.ArrayList m_instrumentedEventLog;
//-------------------------------------------------------------
//Logs the occurrence of an event into an array we can inspect
//
//Note: No attempt is made to keep the size of the
// logging array bounded, so the longer the application
// runs the larger this array will become
//-------------------------------------------------------------
private void instrumented_logEventOccurrence(string eventData)
{
//Create the event log if it has not already been created
if (m_instrumentedEventLog == null)
{
m_instrumentedEventLog =
new System.Collections.ArrayList();
}
//Log the event
m_instrumentedEventLog.Add(eventData);
}
//-------------------------------------------------------------
//Show the list of events that have occurred
//Note: This implementation is pretty crude.
// You may want instead to show the events
// list in a separate dialog that pops up for the
// purpose.
//-------------------------------------------------------------
private void instrumentation_ShowEventLog()
{
System.Windows.Forms.ListBox.ObjectCollection listItems;
listItems = listBoxEventLog.Items;
//Clear the items in the list
listItems.Clear();
//If there are no events, exit
if (m_instrumentedEventLog == null)
{
listItems.Add("0 Events");
return;
}
//At the top of the list show the total of events we
//have counted
listItems.Add(m_instrumentedEventLog.Count.ToString() +
" Events");
//List the items in reverse order, so the most recent are
//displayed first
string logItem;
for(int listIdx = m_instrumentedEventLog.Count - 1;
listIdx >= 0; listIdx--)
{
logItem = (string) m_instrumentedEventLog[listIdx];
listItems.Add(logItem);
}
}
#endif
//-------------------------------------------------------------
//RadioButton1 Changed event
//-------------------------------------------------------------
private void radioButton1_CheckedChanged
(object sender, System.EventArgs e)
{
//If our application is updating the data in the
//user interface we do not want to treat this as
//a user triggered event. If this is the case,
//exit and do nothing.
if(m_userInterfaceUpdateOccuring == true)
{return;}
//Count the number of times this event has been called
m_radioButton1ChangeEventCount++;
#if EVENTINSTRUMENTATION
//Log the occurrence of the event
instrumented_logEventOccurrence("radioButton1.Change:" + //event
m_radioButton1ChangeEventCount.ToString() + ":" + //#times
radioButton1.Checked.ToString()); //value
#endif
}
//-------------------------------------------------------------
//Button1 click event
//Simulates a case where code updates the user interface
//potentially causing event code to be run
//-------------------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)
{
//Indicate that we do not want the event handlers
//to process events right now because we are updating
//the user interface.
//m_userInterfaceUpdateOccuring = true;
radioButton1.Checked = true;
textBox1.Text = "Hello World";
//We are done updating the user interface
m_userInterfaceUpdateOccuring = false;
}
//-------------------------------------------------------------
//TextBox changed event handler
//-------------------------------------------------------------
private void textBox1_TextChanged
(object sender, System.EventArgs e)
{
//If our application is updating the data in the
//user interface we do not want to treat this as
//a user triggered event. If this is the case,
//exit and do nothing.
if(m_userInterfaceUpdateOccuring == true)
{return;}
//Count the number of times we execute this event
m_textBox1ChangeEventCount++;
#if EVENTINSTRUMENTATION
//Log the occurrence of the event
instrumented_logEventOccurrence("textBox1.Change:" + //Event
m_textBox1ChangeEventCount.ToString() + ":" + //# times
textBox1.Text.ToString()); //Value
#endif
}
private void buttonShowEventLog_Click
(object sender, System.EventArgs e)
{
#if EVENTINSTRUMENTATION
instrumentation_ShowEventLog();
#endif
}

Figure 11.4. Pocket PC showing an application with event-logging instrumentation.

Never Leave the User Guessing


Responsiveness is a significant aspect of performance. It is always best to have no delays in the user interface's interactions with the user, but in some cases this may not be possible. When work must be done that forces the end user to wait, extra effort should be applied to make sure that the wait is as comfortable as possible. Think of it as similar to designing a nice comfortable room in your doctor's office. It would be great if you did not need to wait at all, but this seems not to be possible. (If you know of an exception, let me know!) It would be a much worse experience if there were no receptionist to acknowledge your arrival, if you were never told how long you will need to wait, if the surroundings were uncomfortable, and if there were nothing to look at or read to pass the time. Having a fish tank in the room to look at while you wait is an aesthetically nice distraction. Having the receptionist acknowledge your arrival goes a long way as well. The experience is even better if the receptionist tells you how long the wait will be and offers you today's newspaper and a beverage of your choice to keep you comfortable. (Again, if you know of a doctor's office where this occurs, let me know.)Chapter 7, "Step 1: Start with Performance, Stay with Performance," outlines several different levels of user responsiveness and offers an example that shows them. To review this and offer some more detail, the different levels of end-user experience when waiting for a mobile application are as follows:

The worst experience: No responsiveness.
An application that becomes unresponsive and does not offer users any indications that work is being done on their behalf looks an awful lot like an application that has crashed or locked up. The end user anxiety level will quickly climb and they will start attempting to click buttons or tap on the screen to get the application to respond in some way. This leaves a series of random unintended events for your application to process when it returns control back to the user interface. After a short while, the user will probably try to close the application, switch to another application, or yank out the battery of the device to try to reset it if the former strategies do not yield acceptable responses. (Mobile phones make this very easy to do.) Making this situation worse is having a mobile application that does not give the user any visual indication when the work is complete. Assuming users have patiently waited and did not push any buttons or yank out the battery while the application was stalled, how are they supposed to know when the application is responsive again? In all cases, avoid having a user interface that looks like it should be responding to requests but is not.

A better experience: Showing a wait cursor while the application is unresponsive.
A wait cursor serves two important purposes. First, it lets end users know work is being done on their behalf, and second, it lets them know when the work is completed so they can resume using the application. Given that showing a wait cursor is usually a trivial task, there is really no excuse not to at least do this small bit of responsiveness work. Any action that takes more than a few tenths of a second is a user interface stall that the user will notice and there is little reason not to let them know that work is being done on their behalf. A wait cursor will buy you a few seconds of grace and patience from the device's user, but if the work is going to take more than a few seconds you should look at having a more informative progress indicator in addition to the wait cursor. Additionally, if almost every action the user does results in a wait cursor length delay, you should look more at optimizations and background processing for your mobile application's design; delays are fine for some things but not for almost everything.

An even better experience: Showing the user a progress bar or displaying progress text that indicates what is going on.
If the action the user needs to wait for will take more than a few seconds, consider having an updating progress bar or progress text that informs the user of progress that is occurring, the percent of work remaining or an updating estimate of the time remaining to completion. Showing a wait cursor does not prevent you from displaying text that informs the user of the progress of the work. You may want to display some text on the form behind the wait cursor that tells the user what is being done on their behalf. Any information you can give the user that indicates progress is useful. Informative text can be particularly useful in keeping users feeling like they are part of the process. For example, an algorithm that downloads information from a server might display the following incremental progress updates: "Locating server," "Found server, logging in," "Downloading information (10%)," "Downloading information (60%)," "Done!" This kind of information makes the user feel involved in the process. Note the importance of telling them that the action has completed successfully. Further, if there are problems during the download, users may be able to glean information from the progress text that can be used to resolve the problem.

When writing code that periodically updates the user on the progress of a long-running task, you may need to explicitly make the control draw itself. Unless explicitly called to update, forms and controls usually only queue up messages that request they be redrawn; unforced updates occur when the system has spare time to process them. Because your computational work may be occurring on the same thread as the user interface, the work being done will not allow the queued messages to be processed until control is handed back to the user interface. To solve this the .NET Compact Framework supports an Update() method on each control. When this method is called, the control is repainted immediately. Listing 11.5 shows a simple work algorithm that periodically updates the text in a Label control to show progress. Unless a call to label1.Update() is made when the Label's text is updated, the new text will not display to the user until after the work is complete.

A better experience yet: Giving the user progress information and the ability to cancel the operation.
Some tasks may take a significant amount of time, and the user may want to cancel them before they complete. A synchronization task with a server might unexpectedly take several minutes, and the mobile device's user may decide that they need to enter an elevator or go underground to a subway where there is no network access. Alternatively, the end user of the mobile device may need to get to information in another part of the application quickly and cannot wait for the task currently underway to complete. If a task is going to take a long time and is going to block the user interface, it is good design to offer the user the ability to cancel the action being performed on his behalf. This can be done by having your algorithm periodically poll for a flag that is set if the user presses a cancel button. As the chapter on background threading explained, a state machine governing the background task's status is a good way to implement this.

If the long-running task is being performed on the foreground (user interface) thread, offering the user the ability to cancel is more complicated but still possible. As with updating the user interface during heavy processing, responding to user requests such as clicking a cancel button cannot happen unless the thread's user interface messages are being processed. The .NET Compact Framework offers a solution to this and other mobile device frameworks are likely to have a similar concept. Making a call to the static method System.Windows.Forms.Application.DoEvents() forces all the queued up messages on the thread to be processed before execution continues. This means that user interface painting messages will be processed and this also includes processing queued-up button click and key press messages. If you call DoEvents() on a reasonably frequent bases (a few times a second), you can keep the application's user interface responsive to user requests while performing the processing task at hand.

Calling DoEvents() to process a thread's queued-up user interface messages seems like a wonderful cure-all, but it is not. Extreme caution must be used when calling DoEvents() because subtle and highly undesirable effects can arise. Because DoEvents() processes all messages before returning control to the code that called it, things such as timer event handlers can get called and running event handlers can be re-entered as well. It is possible to end up with multiple nested calls into the event handlers for user interface elements and timers if DoEvents() is called inside an event and a new event message has been queued up before the first one is completed. If you are going to use DoEvents(), you must write your application's event handling code very defensively. Putting checks at the top of your event handlers to exit the function if it is being re-entered is highly recommended. Before entering a block of code that will perform a long-duration task and will call DoEvents() in the middle of it, be sure to set the Enabled property of any controls that should not be allowed to generate events to false; this will disable the user from being able to click them and queue up events. There are almost no cases where re-entrant events are desirable; the code gets very confusing to understand and debug. Given the choice between using DoEvents() and creating a background thread to do processing, I would almost always choose a well-designed background threading solution. As complicated as multiple threads are to conceptualize, their execution model is usually simpler and more predictable than the mixture of framework and application code that can get called when using DoEvents(). None of this is specific to mobile devices; the same issues exist on the desktop but given the user's demand for responsiveness in mobile devices it is worth drawing out this issue specifically. Nevertheless DoEvents() can occasionally be useful and I describe it here along with the warning caveat emptor (let the buyer beware!). If you buy the DoEvents() ticket, you will take the ride with all of its ups and downs.

The best experience: Find a way to perform the task in a way that does not block the user.
If your mobile application can push work onto a background thread, enabling users to continue with other tasks and give them a notification when the task is complete, you have achieved the Holy Grail of responsiveness. Doing this requires a great deal of thought about what the user interaction model is for pushing work onto a background thread. Thought must be given to how to keep the user notified of progress and in control of the background processing without being too intrusive to the foreground user interface experience. A good desktop application example of this would be Microsoft Word's support for printing a document in the background while enabling the user to continue to edit the live document in the foreground. It is a useful feature and looks simple, but there is significant design behind it to make it work.


Listing 11.5. Calling a Control's Update() Method to Show Progress Text



//---------------------------------------------------
//This code belongs in a Form containing a single
//Button (button1) and a Label (label1)
//---------------------------------------------------
private void button1_Click(object sender, System.EventArgs e)
{
//Show a wait cursor
System.Windows.Forms.Cursor.Current =
System.Windows.Forms.Cursors.WaitCursor;
string testString;
for(int loop3 = 0; loop3 < 100; loop3 = loop3 + 10)
{
label1.Text = loop3.ToString() + "% Done...";
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//Uncomment the line below to show progress updates!
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//label1.Update();
testString = ";
for(int loop2 = 0; loop2 < 1000; loop2++)
{
testString = testString + "test";
}
}
label1.Text = "Done!";
//Remove the wait cursor
System.Windows.Forms.Cursor.Current =
System.Windows.Forms.Cursors.Default;
}


/ 159