Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Resort Companion

0.00/5 (No votes)
2 Jun 2004 1  
Presenting the Resort Companion, a data-navigation application for vacation resorts

Introduction

I love Disneyland. I've been there quite a bit, and I still enjoy going back. I've thought many times that it would be handy to have a portable application that would help guide people through "the Disneyland experience", but I never sat down to write it. Now, the .NET Compact Framework competition has given me the incentive I needed. Wanting to learn how to use the Compact Framework didn't hurt, either.

Resort Companion is a mobile data-access application that runs on the PocketPC (it will run on the desktop as well, but that's no fun). It's designed to give the user easy access to a database describing a typical resort (in this case, the Disneyland Resort in Anaheim, California). The database itself is easily editable and replaceable, and the user interface is modular, to allow for changes to the database schema.

As a Palm developer, I designed this application keeping in mind the Palm Computing guidelines for mobile applications. These guidelines emphasize speed of use and compact display layout over just about everything else, which I think makes sense for mobile devices of all kinds. The Palm platform has a variety of controls designed to be as compact as possible, while the PocketPC has controls that are mostly designed to mirror desktop controls, so some compromises in the design were required.

Using the Resort Companion

To use the application, just start it up from the File Explorer on the PocketPC. It will display a splash page while it loads and initializes the database, and when it's done it will activate the "Find a starting place..." button. Pressing this button will open the Finder, allowing you to locate a place in the resort. Use the "Question" tab to form a query, and then select the "Answer" tab to see the results. Select a place and click the "OK" button to continue.

The place you selected will be displayed on the screen. The upper portion of the display shows information about the place, while the lower portion shows other places that are nearby. Selecting one of these places will make it the current place. At the very bottom of the display is a button bar with two buttons on it: the magnifying glass will bring up the Finder form again, while the star will mark the currently selected place as a "favorite". Favorite places are added to the pop-up menu attached to the star. Note that your favorites are only stored in RAM, so resetting your PocketPC (or even stopping the Resort Companion app) will clear the list.

Some caveats: the database is far from finished, and in some cases, not even very accurate (in other words, don't rely on this database exclusively...just yet!). In particular, the listing of nearby places is sometimes...well, "generous" in its estimation of "nearby". With time, I hope to have the database fully fleshed out. I've tested this app on the emulator and an HP Jornada, but I didn't have time to test every part of it, so it still could have a nasty crashing bug somewhere.

The Inside Scoop

I've packaged the entire solution into the .ZIP file linked above. It's ready to open in Visual Studio.NET, so you can examine the source, or add your own databases. The database schema is simple enough that you shouldn't have any trouble making updates. If you do update the database, be sure to run the compress.cmd script in the Mobile project. That script uses the DataCompressor utility to compress the XML and XSD files in the Data project, and move them to the Mobile project for deployment. I made a change to my Visual Studio.NET configuration so that double-clicking the .CMD file launches it instead of opening it in the editor (use the "Open with..." menu to set this up yourself). It saves a lot of time.

Behind the scenes, the code is mostly straightforward. The database is loaded into an ADO.NET DataSet from an XML file, using an XSD schema. To save data space on the device, the DataSet is compressed with ICSharpCode's SharpZipLib, and uncompressed during loading. XML compresses very well, and this technique makes deploying XML databases much less storage-intensive. Since I didn't need the all of SharpZipLib's functionality, I created a "streamlined" version that only includes the stream-handling code. If you wish, you can remove that component from the project and use the full SharpZipLib instead (if it's installed in your Global Assembly Cache, for instance).

(from LaunchForm):
// load the resort dataset with the data files
// uncompress the stream on the fly
InflaterInputStream zDataStream = new InflaterInputStream(
    new FileStream(FILENAME_DATA, FileMode.Open));
XmlTextReader dataReader = new XmlTextReader(zDataStream);
this.resort.DataSource.ReadXml(dataReader);
zDataStream.Close();
dataReader.Close();

Once loaded, we decompose the DataSet by wrapping it with a series of custom data objects. Each of these objects exposes the schema of the DataSet while making the data relations explicit. Further, the constructors for each object are memoized, so that a single wrapper object represents each data record. This prevents a data record from getting wrapped multiple times, which would waste memory as well as make object comparisons more complicated.

(from RegionRecord):
static public RegionRecord Maker(DataRow row) {
    if(!(RegionRecord.memo.ContainsKey(row))) {
        RegionRecord newRecord = new RegionRecord(row);
        RegionRecord.memo[row] = newRecord;
    } 
    return (RegionRecord)RegionRecord.memo[row];
}

The user interface makes extensive use of custom controls (though there are many places left where they could beneficially be employed further). Since Visual Studio.NET doesn't provide much support for building custom controls for the compact framework, I had to build them by hand, so they're a little rough around the edges. An unexpected side benefit, though, was that I was able to manage sub-control instantiation more precisely than if I'd used the controls designer. That led to faster form load times and a slightly smaller memory footprint.

An interesting bit of code makes use of these custom controls. In the Finder form, I wanted to be able to list the sub-types of the PlaceRecord class that were defined in the data layer, but I didn't want to tie the "view" classes too closely to the "model" and "controller" classes. The views have to have knowledge of the structure of the sub-types, but hard-coding those subtypes into the Finder itself would have coupled them too closely. So I use a "registry" to manage the relationships.

Each sub-type of PlaceRecord is associated with a sub-type of a view (either a DetailView or a FindView). The base class maintains a static registry of these relationships. Each time a subclass is instantiated, it registers itself as the handler for its associated PlaceRecord class. A client app can then query the base class registry to fetch the correct view object to handle any particular PlaceRecord. The PlaceForm uses this method to choose the correct DetailView to show the contents of the currently selected record.

(from DetailView):
private static Hashtable mapping;
public static Hashtable Labels {
    get {
        Hashtable result = new Hashtable();
        foreach(Type targetType in mapping.Keys) {
            result[targetType] = 
        ((DetailView)mapping[targetType]).TargetLabel;
        }
        return result;
    }
}
public static void Add(DetailView view) {
    DetailView.mapping[view.TargetType] = view;
}
public static DetailView Lookup(PlaceRecord p) {
    return ((DetailView)(DetailView.mapping[p.GetType()]));
}
(from PlaceForm):
// request the appropriate view panel based on the type of place
DetailView viewPanel = DetailView.Lookup(this.place);
// set the local data for the view panel and bring it into view
viewPanel.Place = this.place;
viewPanel.BringToFront();

The Finder uses the Children property of the FindView class to get a "one of each" array of its subclasses. This array is used as a DataSource for a ComboBox. When an item is selected, it's simply brought to the front of the display stack.

(from FinderForm):
// get "one of each" of the specialized find panels for each place type...
foreach(FindView finder in FindView.Children) {
    // ...and attach each one to the "question" tab
    finder.Parent = this.questionTab;
}
. . .
// populate the placeType combo box with the set of finder views
this.placeType.DataSource = FindView.Children;
. . .
// request the appropriate view panel based on the type of place
this.viewPanel = (FindView)this.placeType.SelectedItem;
// set the local data for the view panel and bring it into view
this.viewPanel.BringToFront();

Using the registry technique allows the client app to manipulate the data records and their views "at arm's length", without specifying them directly. This makes it much easier to add new types or make other changes to the record-and-view types without modifying the client app. Eventually I hope to hook up the machinery to support retrieving the UI along with the schema from a web service, to enable dynamic updates to the application to support new databases.

Rough Seas

This was my first Compact Framework application, so I encountered a few rough spots along the way. One of the most glaring was the lack of "floating window" support in the CF. The finder form is faked, in that its title bar is actually just part of the form itself. Floating windows with "real" title bars are not directly accessible through the CF (at least, I couldn't find any…if anyone knows how to make them work, let me know!).

I also discovered what seems to be a bug in (at least) the Compact Framework. I haven't chased it down to determine its scope, but it seems that static members (with initializers) of classes defined in other assemblies don't get automatically initialized before they're used. In my case, the static members of the Data sub-assembly weren't being initialized when they were called from the Mobile assembly. I had to create a static constructor and initialize them by hand there. I'll be investigating this further later on.

Moving On

Some future enhancements:

  • Fully populate the database!
  • Support retrieving the database (and its UI) from a web service
  • Add more retrieval criteria to the FindViews
  • Improve the map drawing and map data handling

What about you? What changes would you make?

History

  • 28 May 2004 - updated source code

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here