Monday, September 3, 2007

How to Handle Control Events?

Object Oriented Programming is awesome. And it is no less awesome when working with User Interfaces. In fact, I find it can be amazingly helpful when you're dealing with a complicated UI that has many interacting components.

I'm going to make a up a really simple UI that has some interacting components so I have an example to work through. Say you have a grid on the left which displays a bunch of records, one per row. When a record is selected it displays the details of that record in a bunch of fields to the right of the grid. This is just your typical master/detail type of view.

One last thing to add. If the user changes one of the details, we want to visually indicate that the record has changed in the grid by adding a '*' at the beginning of the record name.

An OO approach to this might be to make the grid a custom user control and the details pane another custom user control. This allows us to abstract all the logic for managing each component into a separate object. That way its not scattered all over the Form class. I have done this in two different ways and I'm not sure which I prefer yet. For example, with the grid, sometimes I have done this by creating a control that inherits from the grid. Other times I have created a separate class whose constructor requires a grid. And other times I've created a UserControl that wraps the control and exposes the features I need. I really don't know which approach I prefer at this point.

But the title of this post is Handling Control Events, and I haven't talked about events at all yet, so lets get into that. To detect that the user has made a change to the details of a record we're going to have to register a ValueChanged event of some sort on each control in the details pane. We'll do this in the details pane user control. Then when any of those events fire we'll cause our user control to fire its own ValueChanged event. To add the '*' we'll simply have our form hook into the detail pane's ValueChanged event and call a method on our grid control to tell it to mark the selected record changed. Very simple.

But what happens when the user selects a record and we fill in all the detail controls? The value changed event of each control will fire, and our detail pane's ValueChanged event will fire multiple times, and we'll put the '*' on the record's name. But the user didn't actually change the record's details, they just loaded it.

How do we get around this? How do we handle control events? We need a way to tell the difference between the user interacting with the detail controls and us programmatically interacting with the detail controls. I know of three ways to handle this:
  1. Add a "suppress flag" which when true causes the detail pane's ValueChanged event NOT to fire. Set the suppress flag before doing something in code, then unset it when finished.
  2. Remove the events before doing something in code so they don't fire at all, then add them back when finished. This prevents the detail pane's ValueChanged from firing.
  3. Add a CausedBy parameter to the detail pane's ValueChanged event which indicates what caused the change, User or Program. Then the person consuming the event can decide how to deal with it.
Personally, I always go with the first method: suppress flags. This example is trivially simple (and yet I've seen many programmers have problems with it), but when the UIs get more components and more ways to interact these suppress flags can start to get out of control. Unfortunately, I don't know of another way to handle this very common problem.

Do you guys run into these situations?

No comments:

Post a Comment