This is the sequel for this post. In this installment I’m going to cover a simple scenario that I’m always running across: a simple data-entry form, and the synchronization of its buttons used to load, create, or save data. The sample form I’ll cover here is pretty simple, and it looks like this:
That’s nothing more than a simple form to maintain Customer’s data. I’m just capturing the customer’s name, because the amount of data gathered here isn’t relevant to the exercise. The main thing I’m looking for here is the synchronization of the Load, New, and Save buttons. For instance, when the user clicks New, I want all the buttons to become disabled. As soon as the user makes any change to the data on the form, the Save button then becomes enabled. Then, when the user clicks on Save, both Load and New become enabled, whereas Save becomes disabled. Of course, there’s a lot more buttons we’d normally have in such form (such as Delete, Search, etc.), but that’s beyond the scope here.
The quick implementation would be to just go on the form and add some event handlers for the buttons, in which we’d enable/disable other controls accordingly. However, eventually we may want to move those buttons somewhere else (maybe put them a user control, as opposed to directly sitting on the form, or maybe put them in a toolbar or Ribbon control…). Also, we may want other ways to access the functionality (for instance, we may want to press Ctrl+N for New, and Control+S for Save…). The problem is that if all the logic is in the form, we’d have to put too much code in there so to handle buttons being somewhere, shortcuts, etc.
I decided to look at the form as it having different "states": for instance, a "viewing" state, which is the case when the user is viewing some data, and a "dirty" state, which is the case when the user has made changes to the data but hasn’t saved it yet.
Enter the State Machine
The following State Transition Table lists the simple states I’ve identified for my simple form:
|Current State||Event||New State||Action (transition)|
Below is a State Diagram to present another view of the states:
Using the State Machine Toolkit (discussed in Part I), I’ve created a state machine to handle those states. I’ve named the machine DataInterfaceInteractionBase (the idea being that the machine handles the interactions in a "data" user interface…). My default concrete implementation of this machine is called DataInterfaceInteraction. fThe diagram below shows what these classes look like:
The DataInterfaceInteraction class implements the abstract methods (TurnOnDirtyIndicator and TurnOffDirtyIndicator), as well as it overrides a few of the virtual methods (EntryDirty, EntryInitial, EntryNew, EntryViewing). The methods delegate execution to an IDataInterfaceInteractionController, like so:
The diagram below shows the relationship between the state machine and the controller:
Notice the following aspects of the IDataInterfaceInteractionController:
- It has methods that match the ones on the state machine (such as EntryDirty, EntryNew, TurnOnDirtyIndicator, etc.);
- The SetHost method is designed to take in a reference to the state machine that the controller handles;
- The TurnOnDirtyIndicator and TurnOffDirtyIndicator methods raise the TurningOnDirtyIndicator and TurningOffDirtyIndicator events, respectively;
- The RegisterInterfaceController method takes in "data interface controllers" and register them with the Interaction controller. Any number of interface controllers can be registered.
The constructor for that class looks like this:
It takes in an interaction controller, as well as an array of data interface controllers, and then uses those references to register things accordingly.
This is what the IDataInterfaceController looks like:
The properties essentially dictate whether Load, New, and Save features should be enabled or not. The SetInteractionStateMachine method takes an instance of the the interaction state machine that this controller is associated with.
The DataInterfaceInteractionController class
Next, I’ve implemented my default DataInterfaceInteractionController class, which we attach to the interaction state machine. Let’s look at the diagram to see how things are related thus far:
Notice the class has a DataInterfaceControllers collection, which is a Collection of IDataInterfaceController implementers. Nothing too fancy there. The RegisterInterfaceController method is used for adding controllers to that collection:
More importantly, the methods such as EntryViewing and EntryDirty (which get called when the state machine is entering those states), iterate through the data interface controllers and set the LoadEnabled, NewEnabled, and SaveEnabled properties accordingly:
Notice, for instance, that when the user is "viewing" some data, both Load and New are enabled, while Save is disabled. When the user then makes changes to the data, causing a transition to the Dirty state, both Load and New become disabled, whereas Save becomes enabled. In the real world we’d probably have more complex implementations so to decide whether or not some features are enable or disabled (for instance, we may have security restrictions depending on the user, or any other sort of business rule).
At this point we’re only determining which "features" should be enabled or disabled depending on the current state we’re in. That means we’re only programming the behavior here. We don’t have any code at this level that is enabling or disabling buttons, keyboard shortcuts, or anything like that; we leave that up to the implementes of IDataIntefaceController, which will take a look at next.
Hooking up the CustomerEditForm to the Interaction state machine
The CustomerEditForm has an Interactions property, which stores a reference to the DataInterfaceInteractionBase class (which is the abstract baseclass for the interactions state machine):
Setting up the Data Interface Interaction Controller
Next, I’ve created a GetInteractionController method, which instantiates the interaction controller, and sets up event handlers for its TurningOffDirtyIndicator and TurningOnDirtyIndicator events:
The event handlers are just simple anonymous methods. All we’re doing there is to display a little asterisk (*) after the title in the form’s title bar whenever the data is "dirty":
Sending events to the State Machine
It’s necessary to send events to the state machine, so that it can transition to the appropriate states. We’ll first use the buttons located on the customer edit form to send those events:
Since I’ve based my state machine on a "passive" machine, anytime I send events to the machine (by calling the Send method and passing in the event id), I also have to call the Execute method. I created a simple helper method around these calls:
Next, I’ve hooked up event handlers for the Load, New, and Save buttons. Those handlers are very simple, since all they do is to send events to the state machine. I’d also handle any event that would warn me about the data being dirty (such as TextChanged events in this form…):
The first Data Interface Controller
Remember that the Data Interface Controller is the object that enables and disables the features (new, load, save…) accessed by the interaction state machine. We can have as many data interface controllers as we need.
The first implementer for the IDataInterfaceInteractionController interface is the customer edit form itself, since it hosts the buttons that send events to the state machine. The implementation of the interface is as simple as this (one property for each feature):
Nothing to exciting, right? Just some interaction with the Enabled property on the specific buttons.
I’ve then created an InitializeInteractions method, which I call from the form’s constructor. The method looks like this:
A few considerations:
- I call the GetInteractionController to get an instance of the Interfaction Controller that’s to be associated with the interaction state machine;
- I handle the TransitionCompleted event on the state machine (the event handler, shown below, just listens to transition completion on the state machine, and updates the status bar on the customer edit form accordingly);
- The state machine is then instantiated, with two parameters passed to the constructor:
- a reference to the interaction controller;
- an array of IDataInterfaceController objects (which at this point only has a reference to the form itself, since it does implement that interface).
This is the handler for the state machine’s TransitionCompleted event:
At this point, the form is operable. Now I need a few other interface controllers.
Triggering the actions with some fancier buttons
Let’s say that instead of having Load, New, Save buttons placed directly on the form, we wanted to create a fancy user control containing those buttons. Or maybe the buttons were sitting up at a toolbar or ribbon control. It doesn’t matter. The point is that now the form wouldn’t own the buttons anymore. I’ve went with the special user control for this example:
The user control itself (I’ve named it SimpleDataButtons) is a very simple one: it just has the few buttons sitting on it, and some properties were set just to make it look "fancy" (yeah, right…). The class implements IDataInterfaceController, and the implementation is pretty simple:
The SetInteractionsStateMachine takes in a reference to the state machine that this controller sends events to, and the properties (NewEnabled, LoadEnabled…) simply interact with the Enabled property on the controls.
The event handlers for the buttons look pretty much the same as we had on the customer edit form; it just sends events to the state machine:
Now all that’s left to do is to drop the SimpleDataButtons user control on the customer edit form (I’ve named the field simpleDataButtons), and add it to the array of IDataInterfaceControllers:
That’s it. Now when I run the form, I get both set of buttons, and they both work interchangeably (of course, in a real app, I’d have either one or the other…).
Adding shortcuts to trigger the events
Another interface controller that I wanted to add is one that handles keyboard shortcuts. Essentially, I want to access the New, Load, and Save functionality by using Ctrl+N, Ctrl+L, and Ctrl+S, respectively. With the current architecture, this is pretty easy. The "trickiest" part is probably figuring out how to actually handle the keyboard shortcuts. In fact, not too long ago I’ve posted a little design and implementation to do just that (make sure to check out that post!).
For the scope of this post, all you have to know is that I’ve created a DataInterfaceKeyboardHandler (which handles trapping the keystrokes and executing some action depending on the shortcut mapping), and this class implements the IDataInterfaceController interface. Besides receiving a reference to the state machine that the controller sends events to, the implementation of the NewEnabled, LoadEnabled, and SaveEnabled properties essentially does the following:
- The getter returns true or false, depending on whether or not the specific shortcut is registered with in the list of shortcuts;
- The setter, registers or un-registers the specific shortcut, depending on whether a true or false is passed to it.
Again, all the shortcuts do is to send the appropriate events to the state machine. Once again, make sure to check out this post to understand how this DataInterfaceKeyboardHandler class handlers registering, un-registering, and executing keyboard shortcuts.
At this point, it’s only a matter of adding an instance of the keyboard handler to the customer edit form, and add it to the array of IDataInterfaceControllers:
If I run the application now, I can either use the buttons or the shortcuts to access the functionality.
One quick improvement to this design and implementation would certainly be a more generic way to register interface controllers with the data entry forms, but I’ll leave that as an exercise to the reader.
Another improvement would be to add functionality to the State Machine Toolkit’s code generator so that, besides generating the state machine, it could also generate the interfaces for the both the interaction and data interface controllers, as well as a default implementation for the state machine (delegating actions to the interaction controller).
I’m going to also, definitely, clean up names so to make the intention of things clearer, and clean up the design as much as possible.
I also want to play with Windows Workflow Foundation (WF), since it has the option to create state machines. Ideally, it’d be good if I could swap what state machine engines I’m using, without having to change anything else in the application.
Bringing this thing to the point where I have it right now has been really fun. It allowed me to play with and exercise a lot of cool things, such as state machines, delegates, anonymous types, commands, shortcuts, interfaces, controllers, etc.
I’ll keep working on this thing as I move some of this knowledge into some existing applications, and will certainly post anything else I may that could be interesting for somebody else (or may just as reminder to myself…).