Custom Data Binding, Part 1

 

Michael Weinhardt
www.mikedub.net

November 9, 2004

Summary: Michael Weinhardt covers several new designer improvements and additions that allow you to take a single custom type and turn it into a fully-fledged list data source without a single line of code. We also take a look at future enhancements you'll see in Beta 2. (15 printed pages)

Download the winforms11162004_sample.msi file.

Introduction

Recently, I was lucky enough to have the opportunity to visit a friend in Taipei, Taiwan, which was great because I love to travel. In particular, I love trying to learn a little of the local language because I think it's both respectful and adds to the whole travel experience. In Taipei, the language is the modern, standardized version of Chinese, with a Taiwanese flavor (I am no language expert, so apologies for any inaccuracies in my understanding).

Numbers tend to be the first places I start, as I imagine most people do. I tried to get to the stage where I could say the numbers without having to internally translate from English to Chinese. As such, I was looking for new fun ways to practice them. One example is waiting at a pedestrian crossing. In Taipei, a vast majority of crossing signals not only have green and red men, but also have numbers counting down in seconds the time pedestrians have left to cross. It was particularly challenging trying to say the numbers in Chinese as fast as they counted down, and initially, I was phenomenally bad.

Figure 1. Taiwanese pedestrian-crossing countdown

Inspired by the pedestrian-crossing-signals game, I decided to write a Windows Forms application to provide a similar style of computerized game for a wide range of words, numbers, and phrases. The idea was to have the application flash a random sequence of these at you as you attempt to translate to spoken Chinese.

Capturing the Data

The first step was determining what pieces of information would need to be gathered for each word, number, or phrase. I decided on the English representation (what you're translating from), the Pinyin version (what you're translating too), an image of the Chinese character or characters (how it's written in Chinese), and the pronunciation (so you can hear how it sounds).

Note Pinyin is a system of spelling Chinese using the Roman alphabet and, hopefully, making it easier for language-challenged people like me to learn. See http://en.wikipedia.org/wiki/Pinyin for more information.

With this in mind, I set about building a UI to capture the desired game data. From a portability viewpoint, I didn't want to use a database to persist this information. Instead, I chose to use a custom Word type to capture the information, which at some point would utilize the serialization support in the .NET Framework:

[Serializable]
public class Word {

  // Constructor
  public Word(string english, string pinyin, 
              Image character, byte[] pronunciation);

  // Properties
  public string English { get; set; }
  public string Pinyin { get; set; }
  public Image Character { get; set; }
  public byte[] Pronunciation { get; set; }
}

Displaying the Data

Since we'll need multiple words, numbers, and phrases, we'll ultimately need a list data source composed of zero or more Word data items. Subsequently, we need to determine the style of UI most suited to displaying a list of Words. There are two common UI patterns display list data sources:

  • Details view UI
  • List view UI

Details view UIs classically display one list data item at a time, where each public property of the data item is associated with its own control. Because only one data item is visible at any given moment, details view UIs usually provide navigational commands to assist the user in moving back and forwards through the list. Common mechanisms for exposing these commands to the UI are through the menu bar and as a VCR-style tool bar. Figure 2 illustrates a details view UI.

Figure 2. Details view UI

List view UIs, as the name suggests, displays multiple data items using a list or data grid style of control, like the form shown in Figure 3.

Figure 3. List view UI

Details view UIs are great for ad-hoc, infrequent browsing for situations where there are simply too many data item properties to properly display in a list. They are also useful when you need greater flexibility and control over the UI than what is typically afforded you by list style controls. On the other hand, list view UIs suit data entry, particularly when sequentially entering non-trivial rows of data at once. I anticipate entering bulk data for the game and, consequently, think a list view UI is best.

So, to build the UI, we need to turn a single Word type into a list data source, which should then be displayed through a suitable list or data grid style control. In this situation, I think the DataGridView control is a great candidate. Next, we need to ensure any edits are synchronized between the DataGridView and list data source. Because I'm anticipating a lot of data, a VCR-style control is needed to simplify navigation. Any such control should ensure that currency (current list position) is synchronized between it and the DataGridView as navigation occurs in either.

Data Binding

The underlying mechanism designed to support all of these requirements is data binding, which is the ability to hook up, or bind, a data source to a UI control. Once bound, the primary job of data binding is to automatically manage editing and navigation synchronization.

In Windows Forms 1.x, list data sources need to at least implement System.ComponentModel.IList before they can be bound. For developers, this means either constructing their own strongly-typed IList implementations or, at the very least, utilizing one of the .NET Framework pre-baked IList implementations, such as simple type arrays and ArrayList. Once built, a list control's DataSource property needs to be set to the IList implementation to establish the binding. If your data source is a typed data set (.xsd), you can declaratively bind by adding a DataSet component to your form to expose your typed data set to data bound controls on a form. Actual binding is achieved by setting its DataSource and DataMember properties appropriately, as illustrated in Figure 4.

Figure 4. Pre-Windows Forms 2.0 declarative data binding

Alternatively, you could take the programmatic route:

// Bind DataGrid to list data source
this.northwindDataGrid.DataMember = "Employees";
this.northwindDataGrid.DataSource = this.northwind;

Unfortunately, these niceties don't work too well for list data sources based on custom types. First, such data sources are simply not visible to the Windows 1.x Forms Designer. Second, while you can bind an IList implementation to a list control using the DataSource property, you can't enjoy DataGrid's support for automatically detecting and creating columns at design time. Not only that, you'd need to potentially implement several other interfaces to ensure your list data source will support basic editing and navigation.

As you can tell, once you've created your custom type in Windows Forms 1.x, you need to commit to turning it into a useful list data source. That is, unless you are using Windows Forms 2.0, in which case a few mouse clicks and perhaps a minute's worth of effort is all you'll need.

From Little Things, Big Things Grow

To take a custom type, turn it into a fully fledged list data source, bind it to a DataGridView, and add VCR-style navigation requires first configuring your project to recognize your custom type as a data source.

Adding a Project Data Source

A custom type can be registered as a data source in one of two ways. First, by selecting Visual Studio .NET 2005 | Data | Add New Data Source... and so on. Second, by opening the Data Sources tool window (Visual Studio .NET 2005 | Data | Show Data Sources) and clicking the Add New Data Source... link if no data sources exist, as shown in Figure 5.

Figure 5. Empty Data Sources tool window

If your project already has data sources in Visual Studio .NET 2005 Beta 1, you'll need to bring up the Data Sources tool window's context menu and select Add New Data Source... or click the left-most tool bar icon. Whichever way you elect to add a new data source, you'll initiate the Data Source Configuration Wizard and begin by specifying a data source type, shown in Figure 6.

Figure 6. Selecting a Data Source Type dialog box

As you can see from Figure 6, there are four data source types to choose from, including database servers (such as Microsoft SQL Server), local database files (such as Microsoft Access .mdb files), Web services with Web methods that return suitable objects (such as DataSets or custom objects like Word), and objects. And, when they say "object," they mean object (or really they mean "type"). You can choose any type from the next step in the wizard. Figure 7 illustrates selecting the custom Word type.

Figure 7. Selecting the Word object

Although Business Object is used here, you can actually register non-business objects such as your project's application settings, also shown in Figure 7. Of course, we'll select Word, click the Next button, and be left with the expected data source, conveniently added to the Data Sources tool window for us to view and enjoy, as illustrated in Figure 8.

Figure 8. Data Sources tool window with selected object data source

The DataConnector

The next step is the big one —converting our data source from a single type to a proper list data source to which data bound list controls can bind. This is achieved for us by the new DataConnector component. Simply dragone from the Toolbox onto your form and set its DataSource property to the appropriate list data source, shown in Figure 9.

Figure 9. Adding and configuring a DataConnector component

This has the effect of causing the Windows Forms 2.0 designer to generate the following code to InitializeComponent:

private void InitializeComponent() {
  ...
  // 
  // wordDataConnector
  //
  ...
  this.wordDataConnector.DataSource = 
    typeof(winforms11162004_DataBinding.Word);
  ...
}

When this line of code is executed, the DataConnector inspects the data source to determine whether or not it implements IList. If it doesn't, DataConnector creates an internal IList implementation to house the custom type. Specifically, it creates a generic BindingList implementation that we'll discuss later in this article. Note that if the DataConnector is bound to an IList implementation, these steps are not necessary, but the results are the same. Ultimately, DataConnector consumes the custom type and, by proxy, represents it to data bound controls as a valid list data source using its own IList implementation. That's a really neat trick.

Binding the DataGridView by Proxy

We can now drag a DataGridView onto the form and set its own DataSource property. As you may have guessed, that property will be set to the DataConnector itself, allowing it to dutifully fulfill its role as a by proxy list data source. This is illustrated in Figure 10.

Figure 10. DataGridView bound to the DataConnector

Here's the generated InitializeComponent code:

private void InitializeComponent() {
  ...
  // 
  // wordDataGridView
  //
  ...
  this.wordDataGridView.DataSource = this.wordDataConnector;
  ...
}

Because DataConnector is presenting a fully-fledged list data source to a world populated by data-bindable controls, with appropriate data item descriptor information, controls such as the DataGridView can inspect and utilize that information to automatically generate appropriate columns, another feature illustrated in Figure 10 above.

The DataNavigator

The final feature we wanted to implement was VCR-style navigation. Luckily for us, the usefulness of the DataConnector doesn't stop here because it is also turns out to be a currency manager. This means that DataConnector tracks the current data item and implements methods, properties, and events that you can code against to programmatically navigate the list data source.

public class DataConnector : ... {
  ...
  // Properties
  public int Position { get; set; }
  public object Current { get; }
  ...
  // Methods
  public void MoveFirst();
  public void MoveLast();
  public void MoveNext();
  public void MovePrevious();
  ...
  // Events
  public event EventHandler CurrentItemChanged;
  public event EventHandler PositionChanged;
  public event AddingNewEventHandler AddingNew;
} 

Basic navigation using these members would look something like:

private void firstToolStripMenuItem_Click(object sender, EventArgs e) {
  this.wordDataConnector.MoveFirst();
}
private void prevToolStripMenuItem_Click(object sender, EventArgs e) {
  this.wordDataConnector.MovePrevious();
}
private void nextToolStripMenuItem_Click(object sender, EventArgs e) {
  this.wordDataConnector.MoveNext();
}
private void lastToolStripMenuItem_Click(object sender, EventArgs e) {
  this.wordDataConnector.MoveLast();
}

Calling any of these methods ensures that the DataConnector will move to the desired list data item and synchronize any such navigation with and controls bound to the DataConnector so they can update their UIs appropriately.

On the other hand, you might decide that dragging a DataNavigator onto your form and binding it to the DataConnector, once again using the DataSource property, is an easier solution because it achieves the same effect. Not only that, you get Add and Delete functionality thrown in for free. The DataNavigator is a specialized tool strip control that you can extend to expose a wide variety of list data source related activities, such as save and load.

With the DataNavigator added, the completed UI looks like Figure 11.

Figure 11. Completed UI with no code

To get here, we created a custom Word type (not a list), used one wizard, added one DataConnector, one DataGridView, and one DataNavigator, and set three properties. All in all, that's got to be considered easy.

Automatic for the Developer People

But, why spend your time dragging, dropping, and configuring the DataConnector and DataNavigator components? An even easier route is to simply drag the custom data source onto your form, as shown in Figure 12.

Figure 12. Dragging a project data source onto a form

All the work we just did to build-up a UI is performed by the Windows Forms 2.0 designer automatically when you use this technique, and you won't even have enough time to think about making coffee, let alone actually drinking it. And, the code that is generated to make it all work is basically the same code that you would need to write yourselves.

Note At the time of writing, this method only resulted in generating three of the four expected columns. I would expect both approaches discussed in this article to be consistent by Windows Forms 2.0 release, if not Beta 2

The Data Sources tool window actually lets you do a little more magic. For example, you can configure the data source to generate either a list (DataGridView) or details style UI when dragged onto a form. Also, you can specify what sort of control each item property should be generated as. Figure 13 shows both of these items in action.

Figure 13. Instructing UI generation

Finally, if you select Customize from either of the menus shown in Figure 13, you can add to and remove from the list of default choices.

Some Last Minute Adjustments

Of course, the designer can only go so far in what it produces. For example, it can't guess what column order you want and, because a byte array could a wide variety of data formats, it did it's best and interpreted our Pronunciation byte array property as an image although we want to store a sound. To fix the former problem is simply a case of using the DataGridView's column editor to update column order. To fix the latter, I needed to create a custom DataGridView pronunciation column/cell implementation that allows you to single mouse-click each cell to hear the pronunciation. The implementation details are beyond the scope of this article, but check out the February 2004 installment of Wonders of Windows Forms installment for further details, noting that, while there are a few changes between the alpha version I wrote against and Beta 1, the concepts are the same.

Finally, to simplify the process of adding bitmaps and wav files to the Character and Pronunciation columns, I added drag-and-drop support to both. Please explore the code sample for further insight into the implementation details.

With these little fixes in place, the form finally looks like the sample we saw back in Figure 3.

Windows Forms 2.0 Beta 2 Improvements

The technology discussed in this article is based on Windows Forms 2.0 Beta 1. Although I'm going to say it, it goes without saying that things will change. Fortunately, Joe Stegman and Steve Lasker from Microsoft graciously spent some time enumerating the set of updates and improvements you can expect in data binding in Beta 2.

First, a few names have changed, most notably DataConnector will become BindingSource and DataNavigator will become BindingNavigator. Personally, I believe the new names more accurately represent what these components do. That is, they represent and work with binding sources rather than the data sources they expose.

Second, the Data Sources tool window and Data Source Configuration Wizard have been streamlined. The updated Data Sources tool window offers more guidance up front, as shown in Figure 14.

Figure 14. Beta 2 Data Sources window

The same theme is applied to the Data Source Configuration Wizard. The options in the Choosing a Data Source step have been simplified and each option provides descriptive information about what it can do. Figure 15 illustrates this for choosing an Object data source.

Figure 15. More simplified and informative data source selection

The next step in the wizard, selecting the type to bind to, has been improved threefold, as Figure 16 demonstrates.

Figure 15. More functional type binding selection

You can hide or show "Microsoft." and "System." assemblies, XML comments associated with your type are displayed as descriptive text, and the underscores that follow the namespaces in the list, as shown in Figure 7, are now correctly displayed as periods.

Finally, dragging a data source onto a form as a DataGridView will generate well-named DataGridView and DataGridViewColumn names. Currently, the naming is simply the camel-cased DataGridView column type name and a number (dataGridViewTextBoxColumn1, for example). Expect Beta 2 names to be something like the data source field name plus the DataGridView column type name.

Please note that the concepts described in this article won't change, even though the technology will. I would still encourage you to familiarize yourself with Beta 1, if possible.

Where Are We?

Even though I haven't yet completed the application, something must have gone right just by working on the data entry side of things because I successfully ordered ten BBQ pork buns in Chinese. Also, it provided a great way to explore two techniques for taking a single custom type and turning it into a genuine list data source, which relied heavily on the new data-binding workhorse, the DataConnector. DataConnectors ensure edits are synchronized between bound controls and list data sources and, in conjunction with the VCR-style DataNavigator, ensures navigation is also synchronized. The complete development cost was minor, to say the least, especially when compared with Windows Forms 1.x.

However, if you play with the sample, you might notice that certain features are missing, including sorting, searching, and filtering. Although we could implement these on a form, it would make more sense from a reusability standpoint to implement them on the list data source itself, thereby ensuring any data bound control can utilize them. In the next installment, we'll take a look at how to add sorting and searching by implementing IBindingList, which can easily by accomplished by deriving from the new BindingList<T> generic type. We'll also look at adding advanced sorting and filtering by implementing IBindingListView. Finally, we'll add persistence with the .NET Framework's serialization support to complete the game data management part of the application.

Acknowledgements

I'd like to thank the people of Taipei for being very generous, warm, respectful, and considerate. I'd especially like to thank the one person who made the trip possible. Finally, special thanks to Steve Lasker and Joe Stegman of Microsoft for their time and for their input into this piece.

References

Object Binding by Steve Lasker, CoDe Magazine, September/October 2004

Michael Weinhardt is currently working full-time on various .NET writing commitments that include co-authoring Windows Forms Programming in C#, 2nd Edition (Addison Wesley) with Chris Sells and writing this column. Michael loves .NET in general and Windows Forms specifically. He is also often accused of overrating the quality of 80s music, a period he believes to be the most progressive in modern history. Visit www.mikedub.net for further information.