AddThis Social Bookmark Button

Print

Data Management in Windows Forms Applications, Part 1

by Chris Tavares
12/02/2002

I've always enjoyed doing user interface work. The ability to put my work right on the screen in full view is what makes UI work different from back-end business logic. It feels more satisfying somehow to write code that does something on the screen, rather than just adjust an account balance somewhere in some database.

However, in my time working on UI, I've found that most often the interface I'm working on is something like this:

We all know how to handle this simple sort of application. We start by coding up a data class to represent the underlying information:


public class TodoItem 
{
  private TodoPriority priority;
  private DateTime dueDate;
  private string description;

  public TodoPriority Priority 
  {
    get { return priority; }
    set { priority = value; }
  }

  public DateTime DueDate 
  {
    get { return dueDate; }
    set { dueDate = value; }
  }

  public string Description 
  {
    get { return description; }
    set { description = value; }
  }
}

Related Reading

.NET Windows Forms in a Nutshell
By Ian Griffiths, Matthew Adams

Then we start writing the code to read and write from the controls to the data structure. For the form in the above screenshot, it's something along these lines:


private void highPriButton_Click(object sender, 
                                 System.EventArgs e)
{
  todoItem.Priority = TodoPriority.High;
}

private void mediumPriButton_Click(object sender, 
                                   System.EventArgs e)
{
  todoItem.Priority = TodoPriority.Medium;
}

private void lowPriButton_CheckedChanged(object sender, 
                                         System.EventArgs e)
{
  todoItem.Priority = TodoPriority.Low;
}

This is repeated ad nauseum for each control on the form. Basically, we write event handlers so that each change in the UI makes a corresponding change in the underlying data model.

Going the other direction becomes a bit of a challenge, though. How do we get changes in the data model reflected in the user interface? Data can change for any number of reasons. File|Open will load a whole new set of data -- the entire UI will need to be updated. Or suppose your fearless leader comes back and says "Oh, by the way, add a list box to that form so that it shows the due dates for all of your current Todo items." Now you've got two windows that depend on the same data model. Changes in one have to be reflected in the other.

The "easy" way is to simple add code to the Todo edit window to update the Todo list window. But that way lies madness -- the madness of highly-coupled classes. The only reason the Todo editor needs to know anything about the Todo list is to update the Todo list when the data changes. But that has nothing to do with the job of the Todo editor. Good OO design principles say that a class should do only one thing; we're asking the Todo editor to do two: update the data and update its sibling windows. And then what happens when your manager comes in and asks for a third window? What about reusing the Todo editor window in another app?

This is a classic problem in UI code, and there's a classic solution: the Model/View/Controller (MVC) architecture. (Those of us who've done coding with the Microsoft Foundation Classes (MFC) are familiar with the Doc/View architecture, a variation of MVC that merges the controller and view.) The fundamental idea is to split your app into separate Model (or Document) classes that store data and do processing, and View classes that are the actual user interface. As the user does things to the View (mouse clicks, typing, etc) the View class calls methods on the Model class to reflect the changes. The Model class then fires a callback to let its Views know when something has changed. Those of you who are familiar with design patterns will recognize this as the Observer pattern. A UML sequence diagram for our todo app demonstrating this interaction is shown below:

Notice on the diagram that the TodoItem (our Model) is updating two different views. The nice thing about this architecture is that multiple views can share the same model. This way, when the Todo editor changes the date on its Todo item, it calls the Model's DueDate setter method, which then triggers the Model's Update callback, thus causing the Todo list window to update itself. Todo editor and Todo list need know nothing about each other, and each can be reused separately.

Thanks to C#'s event syntax, implementing this in a .NET Windows Forms applicaiton is very simple. All we need to do is redo our model code slightly:


public class TodoItem 
{
  public delegate void TodoItemUpdate( TodoItem source, 
                                       int whatChanged );

  public event TodoItemUpdate Update;

  private TodoPriority priority;
  private DateTime dueDate;
  private string description;

  public TodoPriority Priority 
  {
    get { return priority; }
    set 
    {
      if( value != priority ) 
      {
        priority = value;
        if( Update != null ) 
        { 
          Update( this, 0 ); 
        }
      }
    }
  }

  public DateTime DueDate 
  {
    get { return dueDate; }
    set 
    {
      if( value != dueDate ) 
      {
        dueDate = value;
        if( Update != null ) { Update( this, 1 ); }
      }
    }
  }

  public string Description 
  {
    get { return description; }
    set 
    {
      if( value != description ) 
      {
        description = value;
        if( Update != null ) { Update( this, 2 ); }
      }
    }
  }
}

I've added the Update event, and in each set method I've made sure to fire the event if the property actually changes. I've also added parameters to the event; in this case, the first parameter is the model that's changed, and the second is an integer that tells the client which specific field of the model has changed. This is pretty much what MFC's Doc/View architecture does. It's up to the View class to know what the hint means.

This does work, but it becomes tedious. When writing a new model class, you've got to add setter methods for every properly, and remember to call the update functions. When writing views, you're pretty much forced to take the entire model; even if all you care about is the priority, you have to make sure you properly decode the hints to filter out updates that you don't care about. And it gets really boring, when writing view classes, to write the same code to pull out a string property from the model and stuff it into an edit control for the thirteenth time on the same dialog.

There are several approaches to take to get around this problem. One that I've had some success with uses composite models. The idea is simply this: the "view" class doesn't actually have to be a user interface; it just needs to be an object that registers for updates. You can build up larger model classes from smaller ones. For example, here's a model that handles a single string:


public class StringModel 
{
  public delegate void StringChanged( StringModel source );

  public event StringChanged Update;

  private string s;

  public StringModel() { }
  
  public StringModel( String s ) { this.s = s; }
  
  public void Set( string s ) 
  {
    if( s != this.s ) 
    {
      this.s = s;
      if( Update != null ) { Update( this ); }
    }
  }
  public string Get() { return s; }
}

There's nothing magic here; it's just like the model classes we've written before, except that there's only the single data item. Supposing that we also have a DateTimeModel and a TodoPriorityModel, our TodoItem class now becomes this:


public class TodoItem2 
{
  public delegate void TodoItem2Changed( TodoItem2 source );
  public event TodoItemChanged Update;

  public TodoPriorityModel Priority = 
                                    new TodoPriorityModel();
  public DateTimeModel DueDate = new DateTimeModel();
  public StringModel Description = new StringModel();

  public TodoItem2( ) 
  {
    Priority.Update += 
      new TodoPriorityModel.TodoPriorityChanged( 
                                      this.OnPriorityChanged 
                                               );
    DueDate.Update += 
      new DateTimeModel.DateTimeChanged( 
                                      this.OnDueDateChanged 
                                       );
    Description.Update += 
      new StringModel.StringChanged( 
                                   this.OnDescriptionChanged 
                                   );
  }

  private void FireUpdate() 
  {
    if( Update != null ) { Update( this ); }
  }

  private void OnPriorityChanged( TodoPriorityModel source ) 
  {
    FireUpdate();
  }

  private void OnDueDateChanged( DateTimeModel source ) 
  {
    FireUpdate();
  }

  private void OnDescriptionChanged( StringModel source ) 
  {
    FireUpdate();
  }

}

Instead of manually writing lots of property set and get functions, we simply use member variables of the appropriate model types. Then the composite model registers for updates on changes to its member models. This way, when a view calls model.Description.Set( "A new description" ) the TodoItem object itself will get an Update call. This way the model can react to changes in its contained data. In this case, all TodoItem does is fire its own Update callback.

This gives views a great deal of flexibility. A view can either register with the model as a whole, or with individual sub-items in the model, depending on what it's interested in. This also opens the doors to composite views as well. You could write, for example, a TextBoxView that subclasses System.Windows.Forms.TextBox, stores a reference to an underlying StringModel object, registers for updates on that model, and updates the model appropriately as the contents of the text box change. Then, to hook up to a string in a model, all you'd need to do is drag the TextBoxView onto your form and set its model property.

This worked out pretty well when I did it in C++. However, in the .NET world, this suffers from one big problem: lots of boilerplate code to write. The get and set methods in every atomic model are identical; all that's different are the types. In C++ you can use templates to make writing the models easier, but in .NET, there's no way around just grinding out the code (yet).

You would think that Microsoft would have addressed this problem in the WinForms framework. And they have, but their solution isn't advertised as a general UI update framework. It's Windows Forms Data Binding, which I'll talk about in Part 2 of this series.

Chris Tavares is a development lead on the patterns & practices team, producing written and code-based guidance for .NET developers.


Return to ONDotnet.com