I’ve been doing some research recently regarding State Machines and GUI (Graphical User Interface) interaction. The motivation for this is that GUI interaction is something that keeps coming back at me one way or another, and it’s always something deemed painful by the developers working in the team.
For instance, let’s take the "enable/disabled" kind of logic: depending on different things, controls on the screen may be enabled or disabled (it could be related to a business rule, or to a security rule, or both). I’ve worked on a project years ago that any developer would shiver just because of the bare thought of having to fix a bug or touch code in a form that had a very heavy enable/disable kind of logic. It was probably one of the most fragile pieces of the application.
With UI’s getting more and more complex, I’ve been doing some research on how I could address this scenario in a way that’s flexible and reliable. I’m not sure I have found the silver bullet yet, but I think I’m getting there.
With this post I’ll start a series of posts where I’ll document my findings, and hopefully get feedback from various people.
A quick simple example
The following excellent book has some good chapters on state machine, state diagrams, and state design pattern:
![]() |
Agile Principles, Patterns, and Practices in C# (Robert C. Martin Series) by Robert C. Martin, Micah Martin |
The book has a very quick but good example for a state machine in the GUI interaction realm. I decided to sit down and write some code that implements the example mentioned in the book. I’m going to quote the book’s explanation of the scenario:
"Imagine that you want to allow your users to draw rectangles on the screen. The gestures they use are as follows. A user clicks the rectangle icon in the pallet window, positions the mouse in the canvas window at one corner of the rectangle, presses the mouse button, and drags the mouse toward the desired second corner. As the user drags, an animated image of the potential rectangle appears on the screen. The user manipulates the rectangle to the desired shape by continuing to hold the mouse button down while dragging the mouse. When the rectangle is right, the user releases the mouse button. The program then stops the animation and draws a fixed rectangle on the screen.
Of course, the user can abort this at any time by clicking a different pallet icon. If the user drags the mouse out of the canvas window, the animation disappears. If the mouse returns to the canvas window, the animation reappears.
Finally, having finished drawing a rectangle, the user can draw another one simply by clicking and dragging again in the canvas window. There is no need to click the rectangle icon in the pallet."
After that explanation, there’s a state diagram, which I’ve roughly reproduced in Visio just to help me out here:
A State Machine toolkit and code generator
Somewhere in the book, it explains state machines, the state pattern, and a state machine code generator that the author wrote which is available for download.
The book explains state machines, and it also points out to a code generator that the author wrote that produces code to implement concrete state machines. The core of the explanation can be found in this article. I definitely recommend getting the book, though, since it also offers the code in C#, as well as unit tests that help understanding the design. The SMC (Finite State Machine Compiler) can be downloaded here.
Instead of using the generator mentioned by the book, though, I’ve decided to use the .NET State Machine Toolkit I found on the Code Project website. There are three excellent articles supporting the toolkit, and the articles not only explain state machines, but they also explain the design of the toolkit, how to use it to implement one simple and one slightly more advanced state machine, and how its state machine code generator works and has been implemented. I definitely recommend the reader to check out the tree parts of that article:
A .NET State Machine Toolkit – Part I
A .NET State Machine Toolkit – Part II
A .NET State Machine Toolkit – Part III
The final product of the sample application
The final product of the sample application I built here looks just like a simple form that works as a canvas where the user can draw a rectangle on it. The application changes the mouse pointer as the user draws the rectangle, and also changes it when the user has the left-button pressed but is moving it outside the boundaries of the form. I’ve also implemented a "fancier" version of the drawing algorithm so that it draws multiple rectangles and allows the user to create some simple effects. The fancier version also displays the coordinates of the new rectangle as the user draws it.
Yeah, I know, the application does not look all that exciting, but keep in mind that that’s not the point of this exercise. The idea is that if I have the correct design in place, a developer with good GDI+ plus skills could produce a little component that would take care of rendering some real fancy rectangles. Also, we should be able to take the state machine and implement it, say, in a WPF application.
The Implementation
The quick and dirty implementation of such application would basically override a few methods on the form to capture mouse clicks, do the rendering, etc. If I went that route, what would the code look like when I wanted to provide different implementations of the rendering logic? The code would certainly get very complicated and hard to maintain. Also, what if I wanted to create a WPF version of the application? I’d probably have to rewrite everything from scratch, since the rendering can be accomplished in better ways in WPF than in WinForms.
With all of that in mind I started the implementation of my little application by using the State Machine Maker (from the State Machine Toolkit), to produce a state machine based on the state diagram (showed earlier on in this post):
Here’s a State Transition Table with all the details for our state machine (this is just a simplification of the state diagram presented earlier):
Current State | Event | New State | Action (transition) |
WaitingForClick | MouseDown | Dragging | RecordFirstPoint BeginAnimation |
Abort | StopAnimation | ||
Dragging | MouseMove | Dragging | AnimateRectangle |
MouseLeave | OutOfCanvas | PauseAnimation | |
MouseUp | WaitingForClick | StopAnimation DrawRectangle |
|
Abort | StopAnimation | ||
OutOfCanvas | MouseEnter | Dragging | ResumeAnimation |
Abort | StopAnimation |
The next step was to subclass the generated state machine (that way, if I need to regenerate the machine I don’t lose my changes). I named the subclass WinFormsRectangleToolStateMachine (from here on I’ll refer to this class as "the state machine".
With the state machine ready, I could go ahead and start putting some code inside the "action" methods (such as DrawRectangle, AnimateRectangle, etc.). For instance, the AnimateRectangle method could look like this:
protected override void AnimateRectangle(object[] args) { this.m_SecondPoint = (Point)args[0]; Graphics g = (Graphics)args[1]; g.Clear(Color.White); this.DrawRectangleCore(g); }
Just for the records, args contains both the "second point" selected by the user (bottom-right of the rectangle), as well as the Graphics object to draw the rectangle on.
However, what if I wanted to support different algorithms for this "rectangle tool" of mine? Maybe I’d like to have a ways to draw "simple" rectangles, and/or "fancy" rectangles. In order to support these variances, I’d have to stick all the code in my action methods, and branch them into switch blocks or something along those lines.
That said, if I wanted to support a dozen different ways to render rectangles, my class would look pretty nasty and hard to maintain. Not only that, but what if I also needed to allow other people to write the rendering mechanism? I don’t like the idea of having to give them my source code so that they could add a little bit more mess to it.
Put some Controllers into the mix…
I decided to let my "RectangleToolStateMachine" only handle the change of states, but not put any implementation for the actions right into it. Instead, the machine should call out to a "RectangleToolController", which is the object responsible for actually implementing the actions. With that in mind, I’ve defined the following interface:
public interface IRectangleToolController { void RecordFirstPoint(object[] args); void BeginAnimation(object[] args); void StopAnimation(object[] args); void PauseAnimation(object[] args); void ResumeAnimation(object[] args); void DrawRectangle(object[] args); void AnimateRectangle(object[] args); }
Note that the IRectangleToolController interface declares methods that map to the actions/transitions specified by our state machine.
Next, I’ve changed the constructor for my WinFormsRectangleToolStateMachine so that it takes an instance of an IRectangleToolController:
And then I changed the implementation of my action methods on the state machine so that they delegate the actual actions to the controller (as opposed to implementing the actions itself):
I’ve then created a WinFormsOption1RectangleToolController class, which implements the IRectangleToolController interface, and filled in the methods with the code that handlers the rendering of simple rectangles.
Now when it comes to using my state machine and controller, it’s all a matter of instantiating the machine and pass a controller into it:
Triggering the transitions
The only part that’s left is to trigger the transitions on the state machine. Since my machine is used by a simple form (which I called "Canvas"), all I’ve had to do was to hook up to mouse events and trigger the appropriate transitions.
Every transition on my machine expects the same parameters: the Graphics object where we draw on, and the location where the mouse has been either clicked or released. I created a simple method that gathers those two parameters, send them to the state machine, and tell the machine to execute the transition:
private void SendEventToMachine(int eventId, Point mouseLocation) { Graphics g = this.CreateGraphics(); object[] args = new object[] { mouseLocation, g }; m_RectangleToolSM.Send(eventId, args); m_RectangleToolSM.Execute(); }
And finally, my handlers for the mouse events on the form look pretty much like this:
In order words, the handlers just send the appropriate events to the state machine. There’s no code in the form whatsoever that deals with rendering rectangle; that responsibility is delegate to the controller associated with the machine (whose only responsibility is to handle the transitions).
Implementing other controllers
Just to try out my design, I created a "FancyWinFormsOption1RectangleToolController" class, which renders much fancier rectangles, and it also displays coordinates on the canvas as to where the rectangle is being rendered (wow! well, ok, I was just trying out the design here, so it’s not like I’ve put a lot of effort into coming up with better ways to render rectangles…).
The fact is that all I had to do was to:
- Create the new controller class, making it inherit from my IRectangleToolController interface;
- Implement how the controller handles rendering rectangles and the other methods exposed by the interface;
- Let my Canvas form know about my new controller.
Nothing else had to be changed. I could certainly have made it even better, by going with a plug-in architecture, where new controllers could be made available to the application just by registering them somehow (but that was beyond the scope of this exercise).
Coming up next…
I’ll write up another post to follow this one. On that new post I’ll describe a design and implementation that I’m considering to address a common UI interaction need. With that post I’ll make available all the source code so that you can it out and hopefully give me some feedback (I’m not posting the code just now because I have some clean up to do). 🙂
#1 by Omer on August 16, 2008 - 2:36 pm
Hi,I wanted to mention that the State Machine Toolkit is now being maintained as an open-source project in sourceforge. It can be found here: https://sourceforge.net/projects/smtoolkit/I added generic support for it and a few other nice features.– Omer Mor.