An Uncommon Solution to a Common Compact Framework Problem

he Common Dialog API has been a solid staple in Windows development for more than a decade. In fact, not only is it a useful source of reusable code, it is a fundamental piece of the Windows look-and-feel, providing a familiar interface for users to navigate and interact with the system. As with anything so venerable, it’s not surprising that the API has evolved. But it is surprising that it has adapted so readily.

In the beginning, the Common Dialog API was unlike the classes and controls familiar to many developers today, mostly because the Windows SDK was originally geared for a C compiler. Developers could?and routinely did?import and alter resource files or hook the dialog’s window proc and intercept Windows messages to add a new control. Things got even easier when the MFC (Microsoft Foundation Classes) introduced an object-oriented approach.

Building on that familiarity, it was entirely expected that the System.Windows.Forms namespace of the .NET Framework would include a CommonDialog class and its progeny (ColorDialog, OpenFileDialog and so on)?and it does. While the .NET classes do not provide the same degree of fine turning that the older APIs do, there are still some extensibility features built in. Alas, even these more limited extensibility features do not appear in the first version of the Compact Framework on the Pocket PC. Worse, the designers of the Compact Framework made a few other choices for the Pocket PC implementation that?while perhaps sensible on their face?do not allow for the rich flexibility developers enjoy on the desktop.

Most notable is that the OpenFileDialog and the SaveFileDialog classes on the Pocket PC are restricted to the device’s My Documents directory. Although there are well intentioned reasons behind this restriction, it can sometimes become more of a hindrance than a help during development. Many applications need to browse the entire file system of the device, most commonly for files located on storage cards. But regardless of the reason for viewing other directories, if you need to tailor the user interface, you have no recourse.

While you can find a few homebrew solutions using various Web search engines, they are typically limited application solutions with no thought toward extensibility. This article covers a set of classes that not only offers an alternative for the lacking Pocket PC classes but an API specifically intended to allow you to extend the interface.

Why Reinvent the Wheel?
You could ask, “Why not just use the existing common dialog DLL and just make the appropriate P/Invoke calls?”

That’s a valid question. The answer is, “Yes, you can absolutely do that.” In fact, cursorily plumbing the System.Windows.Forms with tools such as SharpDevelop and Lutz Roeder’s Reflector provides a fair indication that Microsoft is leaning on some native calls for at least some functionality beyond normal form handling (moreso since the base CommonDialog class does not inherit from Form but instead from Component).

Creating P/Invoke wrappers does let you create dialogs that can cruise the entire file system and already have all the expected standard functionality; however, that doesn’t let you extend those dialogs through managed code. For example, if you wanted to add a new button you would need to make a number of additional unmanaged calls. Although that’s not terribly difficult, it is hardly a managed solution. Unfortunately, the problem is muddied further because OpenFileDialog and SaveFileDialog are sealed classes. So, even if you went through the trouble of writing all the unmanaged calls and wanted to package it up you would need to create a wholly new control that provided the same interface as the CommonDialog subclass you were extending. That seems like an unreasonable amount of work just to make a “simple” addition to the interface.

Designing the UncommonDialog

?
Figure 1. OpenFileDialog/SaveFileDialog. The figure shows the standard Open and Save dialogs for the .NET Compact Framework on the Pocket PC.

The UncommonDialog classes described in this article?provide a richer set of extensibility features than the CommonDialog classes for the Pocket PC’s Compact Framework CommonDialog implementation. The initial goal of this library is to address the shortcomings of the OpenFileDialog and SaveFileDialog classes (see Figure 1). However, these two dialogs are merely a subset of the Common Dialog API’s broader purpose of providing a constituent and coherent user interface for a wide range of tasks, such as font and color selection and some printing functions. As it stands, many of these features do not exist in the current version of the Compact Framework on the Pocket PC. The forthcoming .NET 2 Framework will address many of these design restrictions, but many will remain?specifically those on the OpenFileDialog and SaveFileDialog classes.

In addition to providing more extensibility, the UncommonDialog classes make an effort to provide a user interface similar to that of the Pocket PC’s File Explorer. OpenFileDialog and SaveFileDialog offer distinctly different looks than the standard tools of the Pocket PC. In fact, the OpenFileDialog class on the Pocket PC is downright Spartan.

The core, though not the entirety, of the UncommonDialog class API is composed of 11 classes that provide the three usable Form-based dialog controls: OpenFileBrowser, SaveFileBrowser, and FolderBrowser (see Figure 2). These three control classes derive the bulk of their functionality from the FileSystemBrowser class.

?
Figure 2. UncommonDialog UML Model: The UncommonDialog class API contains 11 classes that provide form-based OpenFileBrowser, SaveFileBrowser, and FolderBrowser dialog controls.

The UncommonDialog Class
This class is the base class for everything in this API and as such provides the least amount of functionality so as to provide the greatest amount of commonality. This frugal class provides three protected properties (see Table 1) and only one protected method (see Table 2).

Table 1. The table lists the UncommonDialog class’s protected properties.

Property Description
DialogMenu Returns the form’s main menu.
DialogToolbar Returns the form’s toolbar.
Panels Returns a NamedPanelCollection. This collection does not contain anything by default.

Table 2. The table lists the UncommonDialog class’s sole protected method.

Method Description
InitializeDialog This method provides an opportunity to initialize the display just prior to it being displayed. Each subclass that overrides this method should make certain to call the superclass’s implementation before adding any new functionality.

The FileSystemBrowser Class
The FileSystemBrowser is a key class in the hierarchy of the UncommonDialog API, providing both a user interface much like the Pocket PC’s File Explorer and a developer interface with ample extensibility options (see Figure 3).

?
Figure 3. FileSystemBrowser Snapshot: The figure shows the FileSystemBrowser running on a PocketPC.

The class provides several protected properties (see Table 3) and methods (see Table 4) that are important for use by its subclasses.

Table 3. The FileSystemBrowser class has several protected properties.

Property Description
ComparerCollection Returns the NamedComparerCollection created by the CreateComparerCollection method.
CurrentComparer Returns the IComparer currently being used to sort the display.
CurrentDirectory Returns a DirectoryInfo instance that represents the directory currently being displayed.
FileMask Returns the file mask string used to determine the current display. By default this value is “*.*”.
ListView Returns the ListView being used to display the active contents.
ListViewItemFactory Returns the IBrowserListViewItemFactory implementer created by the CreateListViewItemFactory method.
SelectedElement Returns the currently selected TaggableListViewItem.

Table 4. The table lists the FileSystemBrowser class’s protected methods.

Method Description
CreateComparerCollection The factory method for creating a NamedComparerCollection.
CreateListViewItemFactory The factory method for creating an IBrowserListViewItemFactory implementer.
DisplayDirectory This method is the core of the logic that displays the contents of the current directory. Both FilterDirectory and FilterFile are called from this method.
FileSystemContentSelected This method is called whenever the user selects something in the ListView.
FilterDirectory This method returns a Boolean determining whether or not a directory should be displayed. The base implementation checks the Hidden and System file attributes against the ShowHidden and ShowSystem properties.
FilterFile Like FilterDirectory, this method determines whether or not a file should be displayed.
OnDeleteButton This method is called when the Delete toolbar button is pressed. The base implementation will delete the currently selected directory or file.
OnNewFolderButton This method is called when the New Folder toolbar button is pressed. The base implementation will create a new folder in the current directory.
OnPropertiesButton This method is called when the Properties toolbar button is pressed and will load the PropertiesDialog form which will display the various file system data bits about the selected directory or file.
ToolbarButtonClickHandler This is the raw event handler for the toolbar. If new buttons are added this method should be overridden.

If you examined the UML model shown in Figure 2, two important classes readily become apparent: FileSystemBrowserListViewItemFactory and FileSystemComparerCollection. A class that implements the IBrowserListViewItemFactory interface configures the ListView that is central to the File Explorer-like view. The IBrowserListViewItemFactory interface defines a property for retrieving an array of ColumnHeaders and a method for creating a TaggableListViewItem, which is merely a subclass of ListViewItem that has a Tag property like its standard Framework cousin. This Tag property serves to associate System.IO.FileSystemInfo instances to be associated with each entry in the ListView. The other class, FileSystemComparerCollection, a subclass of NamedComparerCollection, provides a collection of System.Collections.IComparer implementers for sorting FileSystemInfo objects by various fields such as name, date, or size exactly as you can with the File Explorer.

The FileSystemBrowser class provides two virtual factory methods, CreateListViewItemFactory and CreateComparerCollection (noted in Table 4), so that subclasses can provide alternate implementers for customization. In addition to these two methods, there are a number of other virtual methods that let developers handle dialog control events such as the toolbar’s button-click event.

The controls of the FileSystemBrowser are packaged neatly within two panels. The navigation and the sort dropdowns are contained within a panel named “Navigation” and the ListView within a panel named “Display.” You can retrieve both panels from the NamedPanelsCollection. This contained layout makes it easy to manipulate blocks of the user interface instead of having to deal with the discrete pieces individually.

The OpenFileBrowser and SaveFileBrowser Classes
The OpenFileBrowser and SaveFileBrowser classes are meant to be drop-in replacements for the OpenFileDialog and SaveFileDialog classes, respectively. These UncommonDialog subclasses offer public properties and default functionality identical to the CommonDialog subclasses in the Compact Framework. This means that to use the UncommonDialog library a developer needs to add only a single namespace import statement (using in C# and Imports in VB.NET) and change a variable’s type.

Building an MP3 Browser
After you’ve kicked the tires on the OpenFileBrowser and SaveFileBrowser to determine that they do indeed serve well as replacements to their CommonDialog analogs, it’s time to test the extensibility features by building this useful MP3 file browser. Because the MP3 file format carries metadata in what are called “ID3 tags,” you can write code that leverages the metadata to provide extra information to help users select files.

To begin, I created a FileSystemBrowser subclass called MP3Browser, using Joel Mueller’s ID3 code (see the sidebar “Choosing an ID3Tag Library“), making it a direct subclass of FileSystemBrowser rather than OpenFileBrowser because the latter is intended for a broader sweep of capabilities?such as being able to select different file extensions. The MP3Browser should display only files with the MP3 extension, but it must be able to view and sort them based upon information stored in their ID3 tags rather than the usual file system information such as file name or size.

The MP3BrowserDialog
Creating the browser dialog itself is simple. By inheriting directly from the FileSystemBrowser class you need only implement five methods; InitializeDialog, CreateComparerCollection, CreateListViewItemFactory, Filename, and FileSystemContentSelected (see Listing 1).

CreateComparerCollection and CreateListViewItemFactory just return new instances of two classes discussed in a moment. The InitializeDialog method calls its superclass’s implementation and sets the default file mask (through the protected FileMask property inherited from FileSystemBrowser) to *.mp3. The Filename property keeps track of the user-selected MP3 file, which is set during the overridden FileSystemContentSelected method. Whenever a user selects an item in the ListView the FileSystemBrowser class calls the FileSystemContentSelected method, passing a TaggedListViewItem. The TaggedListViewItem’s Tag property contains the FileSystemInfo object which lets the browser’s logic track what MP3 file the user selects.

?
Figure 4. MP3Browser Snapshot: The figure shows the MP3 browser browsing the ID3 tags of the MP3 files in the Music directory.

The ID3TagListViewItemFactory Class
To accommodate the necessary changes to the ListView’s display, the MP3Browser uses a new IBrowserListViewItemFactory implementation class, called ID3TagListViewItemFactory. The CreateListViewItemFactory method (which is automatically called by the FileSystemBrowser) instantiates this class?you need only override it. This implementation defines three columns, Track, Song and Artist, which it populates by extracting the Track, SongTitle, and Artist ID3 tag fields. The class creates the columns in its constructor and stores them to a private array. The bulk of the interesting code is in the CreateListViewItem method shown in Listing 2.

As discussed previously, this method is responsible for creating a TaggableListViewItem instance using some data provided to the method in the object argument. In this case the object will be either a System.IO.DirectoryInfo or a System.IO.FileInfo since the class browses the file system. If the argument is a FileInfo object, then the browser calls Joel’s ID3Reader class to extract the ID3 tags and populate the columns (see Figure 4).

One of the most important bits of logic?stashing the FileSystemInfo object to the Tag property?occurs just before the method returns the TaggableListViewItem. This is a vital action because it provides the glue between the user interface and the file system being browsed.

The ID3TagComparerCollection
The simple ID3TagComparerCollection class inherits from NamedComparerCollection. In its constructor it loads three System.Collections.IComparer implementers. These IComparer instances expect the incoming arguments to be FileSystemInfo objects. If they are FileInfo objects the class extracts their ID3 tag and runs a text comparison against them.

After stitching all these things together you’ll have a useable MP3Browser class.

The MP3PropertiesDialog
Observant readers will notice that there is actually a sixth method in the MP3Browser code file, called OnPropertiesButton, which takes the MP3Browser class a step further overriding the properties button to display a subclass of the PropertiesDialog.

Inspecting the PropertiesDialog code, you’ll find that all its visual controls are contained within Panels in exactly the same fashion as the FileSystemBrowser. So I merely created a new Form in Visual Studio, put two tabs on it and placed some fields for the ID3 tag information on the first tab. After building the layout, I closed the designer, opened the code view and changed the inherited class to the PropertiesDialog (this functionally becomes an irreversible step since Visual Studio does not support visual inheritance for Compact Framework forms).

As you can see from the code listing there were only two methods I needed to override to achieve my goal, InitializeDialog and ProcessPropertyObject.

   protected override void InitializeDialog()   {      base.InitializeDialog();      foreach(string panelkey in this.Panels.Keys)      {         System.Windows.Forms.Panel panel = this.Panels[panelkey];         this.tabPageFileInfo.Controls.Add(panel);      }         this.textBoxSongTitle.Enabled = false;      this.textBoxArtist.Enabled = false;      this.textBoxAlbum.Enabled = false;      this.numericUpDownTrackNo.Enabled = false;   }

Leveraging the Panels collection provided by the superclass, I just moved all the panels containing the controls used to display the FileSystemInfo properties to the second tab. This is done in the InitializeDialog method; nothing very intricate or special about the effort, just the easy benefits of a little planning.

   protected override void ProcessPropertyObject()   {      base.ProcessPropertyObject();         Mueller.Utils.ID3.ID3Tag tags = null;         tags = Mueller.Utils.ID3.ID3Reader.GetFileInfo(        (System.IO.FileInfo)this.PropertyObject);      if(tags != null)      {         this.textBoxSongTitle.Text = tags.SongTitle;         this.textBoxArtist.Text = tags.Artist;         this.textBoxAlbum.Text = tags.Album;         this.numericUpDownTrackNo.Value =            int.Parse(tags.Track);      }   }
?
Figure 5. MP3PropertiesDialog: The dialog lets you inspect the ID3 tags and file properties of an MP3 music file.

The PropertiesDialog does its real work in the ProcessPropertyObject method. This method gets called just before the form is displayed. The class uses the object passed during construction, retrievable through the classes PropertyObject property (which is either the DirectoryInfo or FileInfo object obtained from the currently selected TaggedListViewItem at the time the user clicks the Properties button), to populate the form fields. To preserve that behavior, it calls the superclass’s ProcessPropertyObject method immediately and then reads the ID3 tags from what is assumed to be a FileInfo object. Figure 5 shows the PropertiesDialog.

This high-speed tour through the still emerging UncommonDialog API demonstrates not only a drop-in replacement for some of the CommonDialog subclasses but also an extensible system meant to keep pace with the needs of more configurable systems. The MP3Browser demonstration project shows the kind of flexibility that you can easily extend to browser subclasses for viewing FTP or WebDAV servers, or?with a little more effort?adapted to work with LDAP hierarchies.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

The Latest

iOS app development

The Future of iOS App Development: Trends to Watch

When it launched in 2008, the Apple App Store only had 500 apps available. By the first quarter of 2022, the store had about 2.18 million iOS-exclusive apps. Average monthly app releases for the platform reached 34,000 in the first half of 2022, indicating rapid growth in iOS app development.

microsoft careers

Top Careers at Microsoft

Microsoft has gained its position as one of the top companies in the world, and Microsoft careers are flourishing. This multinational company is efficiently developing popular software and computers with other consumer electronics. It is a dream come true for so many people to acquire a high paid, high-prestige job

your company's audio

4 Areas of Your Company Where Your Audio Really Matters

Your company probably relies on audio more than you realize. Whether you’re creating a spoken text message to a colleague or giving a speech, you want your audio to shine. Otherwise, you could cause avoidable friction points and potentially hurt your brand reputation. For example, let’s say you create a