Creating a Multiple Form Application Framework for the Microsoft .NET Compact Framework
Applied Data System
Microsoft® .NET Compact Framework 1.0
Microsoft Visual Studio® .NET 2003
Summary: Learn how to create an efficient User Interface engine for your .NET Compact Framework-based applications. (8 printed pages)
When designing almost any application, one of the first design considerations is how to implement Graphical User Interface (GUI) navigation—that is how you present the user with new screens of information. Traditionally in the PC environment an application simply created a new Form (or Dialog) for each screen of information.
This worked fine until developers started using Microsoft® eMbedded Visual Basic® (eVB), where it quickly became evident that an application with a lot of forms tended to use up precious memory resources and that if you needed something like a menu on all of your Forms, you ended up duplicating large sections of code to keep behavior consistent.
The workaround that many eVB developers settled upon was to use multiple Frames on a single Form. Now that developers are using the Microsoft® .NET Compact Framework, it is once again time to examine methods of building an efficient User Interface (UI) engine that isn't tightly coupled to the application using it.
With today's PCs, desktop application developers rarely even consider the implications of their on memory usage. Developers targeting smart devices do not have that luxury. One way to keep memory usage minimized is to reuse classes, specifically Forms, whenever possible.
For example, let's consider a kiosk application that you might find at your local video rental store. Let's assume this application starts by presenting a list of all movies they carry. Admittedly this would be a big list and is a poor application design, but for our purposes lets assume this is reasonable.
Now if a user selects a movie title, the application displays another Form that gives detailed information about that movie, including the actors in it, and another list of suggested movies that are similar ("if you like this movie then you might like . . . ").
Figure 1. Screen Flow Diagram
From here, let's assume the user can either select a movie from the new list to just repopulate the current Form with new information about that movie, or they can select an actor. Doing so would now show information about that actor, including perhaps co-stars and again another list of movies, this time a list of movies this specific actor has been in.
It is quite obvious that the application would quickly use a lot of memory if every Form viewed was a new Form instance carrying all of its associated data. In addition, Windows CE devices often have processors that don't quite match up to a desktop PC for performance, and loading each one of these Forms with new data could produce a slow, unresponsive and user-unfriendly application.
Let's throw in another nice feature for the user—the ability to view the previous Form, like you get with a "back" button with a browser. This means the application must also "remember" where the user has been.
In some applications it is simple to do because the "to" and "from" paths for a Form might be limited enough that this could be hard coded. For our application, though, you can see that the order in which these Forms could be presented to the user is nearly random (see Figure 1).
So we now know our general requirements. First, we want to minimize the number of Forms we hold in memory, second we want to keep a history of what forms the user has viewed and in what order. In fact we would probably want to track what data was being viewed on each Form as well, but once we've got our framework done, it is a simple addition and I'll leave as an exercise for you the reader.
So what solution fits these requirements? To minimize the number of Forms we can use what is known as a cache. A cache is simply a storage mechanism in which we keep a single copy of Forms we've already loaded and reuse cached Forms whenever possible. This means that when a user wants to view a Form, first we see if it's in the cache. If it is not, we load it as usual, but if not we simply display the cached Form and save both the memory a new copy would require plus the time it takes to actual create the Form.
Keeping track of the history of Forms viewed can be done using a stack. Imagine, if you will, that each screen viewed can be represented as a card and these cards are put into a deck, or stack. As a user views each screen, the card would be placed (pushed) onto the stack. This means that the top card would be the last viewed Form and that to view a history, you simply remove (or pop) a card from the stack. In this way the application can "undo" the navigation done by the user for any number of Forms in any order.
Now that we have an understanding of the requirements and the general method by which we'll meet them, let's look at how this would be implemented. First let's consider in a little more detail exactly what we should do to make this work.
We'll need the FormStack itself, which we can implement as a Class. It needs to have a mechanism for Pushing and Popping Forms. It needs a cache to hold actual Forms and a stack, which, because of the resource issue we already discussed, shouldn't hold actual copies of the Forms, but instead an identifier. We'll use the Form's class type as a string.
Loading a Form is not always instantaneous especially when it contains many controls. You have to create all of the Form's controls, often you may need to fetch data for lists and dropdowns, and once you've fetched data, controls have to be populated. If we do all of this activity on the application process' main thread, the user may end up having to wait, during Form load, longer than we would like.
To help decrease this wait, we'll add two features. First, we'll use a worker thread to fetch any needed data and second, we'll add a method to allow the application to "preload" a form into the cache. This would allow the application to load "heavy" Forms in the background or during idle times such as during a splash screen.
There are a few other niceties we'll add as we implement the cache and stack, but we've now defined the core of what we need. Almost all of the actual work done by our FormStack class will be initiated during either a Push or a Preload, so let's look at the logic behind each.
Figure 2. Push Execution
Figure 2 shows an execution flow diagram for a Push. As you can see, we simply check the cache to see if it's there, if it's not, we call Preload; otherwise we simply show the Form and add it to the stack.
Figure 3. Preload Execution
Preloading (seen in Figure 3), while involving a few more steps, is still straightforward. First, we must create the Form class. Before calling the standard InitializeComponent method to create all of the Form's controls, we start the data query thread.
An important step is that once the controls have all been created we must wait for the data query thread to complete before we populate the controls, so we'll have some basic thread synchronization to deal with.
After we've populated the controls, the last step is to add it to the cache.
The actual code implementation of the FormStack is not much of a departure from these flow diagrams. First I created a StackForm base class that all Forms must inherit. This assures the existence of the general framework, such as the data thread and the control population methods even if your Form doesn't use them.
The StackForm does the following:
- Adds an EventHandler to intercept the Closing event on the form so we can call Pop when a Form is closed instead of actually unloading it.
- Provides the LoadData worker thread function and the Populate function, which gets called after the workerthread completes.
- Provides a public abstract method signature for InitializeComponent. This is important because the Form Designer makes InitializeComponent Private by default but the FormStack class needs to call it. By providing an abstract implementation the compiler will make sure we make this change.
Unfortunately, the Form Designer in the Visual Studio .NET 2003 doesn't work with forms inherited form anything but the Form class. What I have done is design my Forms using the designer while they inherit from Form and then changing the base class to StackForm when I build. It's a slight inconvenience, but is better than having to hand-code the forms.
Next I created the FormStack Class. The FormStack implements Push and Preload just like the flow diagrams. The stack itself is implemented using an ArrayList to make it simple to add or remove items. The form Cache is implemented by having the FormStack Class inherit from CollectionBase. This provides a List member collection that greatly simplifies storing the FormStack objects.
In addition to Push and Preload I've implement Pop, which simply removes the top item from the stack and displays the next StackForm down. It does not remove the StackForm from the cache and I didn't implement a function that does. While a method to actually release a form from the cache may be useful in some scenarios, it adds complexity beyond the scope of this article.
One of the larger hurdles this architecture presents is actually firing up your application. The .NET Compact Framework provides the entry point Application.Run, which loads up a Form and provides a message pump. Using Run will not work in our architecture because it requires an instantiated Form and every other Form in the application must run within that initial Form. Instead we need to provide our own message pump that can dispatch system messages plus provide a simple way for the application to actually end.
While this may seem complex, it is actually quite simple. I implemented the message pump in FormStack.Run by using a tight loop that simply calls Application.DoEvents as long as the cache has any Forms in it. This means you must first Push a StackForm onto the stack before calling Run. I've also implemented the complementary method Stop which simple clears the cache, which in turn causes the message pump to exit and Run to complete.
Finally I overrode StackForm.ToString. This simply provides a nice string that lets you know how many StackForms are in the cache and the exact contents of the stack.
The last thing we need to look at is the actual implementation of a StackForm. Since StackForm is an abstract class, we can't actually instantiate one. Instead we must inherit the StackForm in our own derived class. Inheritance is one of the beauties of object oriented programming and can get pretty complex, so I will assume you already have a good grasp of it and simply cover the specific things to be mindful of when inheriting a StackForm.
First the constructor must call the StackForm base constructor. This is because the base constructor wires in the Closing event.
Next the default constructor calls InitializeComponent. In our implementation, the FormStack calls InitializeComponent, so we don't want the constructor to actually call it, but since we do want the designer to be able to render our Form, we need the call to be there. Nicely enough the .NET Compact Framework has a compiler directive that allows us to exclude the call like this:
#if NETCFDESIGNTIME InitializeComponent(); #endif
Lastly we have the option to implement the DataThread and Populate methods if we so desire.
Again, DataThread is executed on a separate thread and should be used to fetch data. Keep in mind that the StackForm will not be fully Pushed and cannot be displayed until after the DataThread completes its work, so don't implement anything that might block execution like calling Sleep. If you choose to implement the DataThread the last item you need to do (and this must get done to signal thread completion) is to call base.DataThread.
Finally Populate gets called after the InitializeComponent and DataThread methods complete so you can use that as your opportunity to fill lists, dropdowns or other controls before the StackForm become visible.
Keep in mind that since these StackForms get cached, DataThread and Populate will only get called when they are loaded into the cache, not each time they are displayed.
As I alluded to early on in this article, as you develop more complex applications you may find the FormStack is missing some functionality such as the ability to refresh data based on what should be displayed. Keep in mind that the stack variable itself could easily hold not just the Form type but possibly the entity ID value from a database to allow quick repopulation of the Form.
The great thing about programming is that you can always add additional functionality, but always keep in mind that Smart Device programming is inherently different than desktop application programming. Device resources are usually limited and processor speeds are typically much slower, so as an application programmer you have to keep both in mind if you want to develop a successful application. The FormStack is a tool to help you as a developer minimize your application's resource impact while managing the complexity of a GUI engine which provides quick Form changes through caching and easy navigation through a stack.