Controlling Multiple Forms with a Finite State Machine






4.70/5 (14 votes)
This article presents a method for controlling multiple Forms with a finite state machine
Introduction [toc]
A paradigm that I have successfully used to implement multiple interrelated forms is the finite state machine. It makes a few demands upon the forms and the Dispatcher. But it is simple, surprisingly efficient, and easily extensible.
For example, say that an application requires four forms:
- initialize - execute startup code.
- login - verify the user's credentials.
- order - accept a user's order.
- realize - confirm the user's order
The flow of control between these forms is depicted in the following figure. A square rectangle represents a form; a rounded rectangle represents a Button; the dotted green rectangle is the start state of the application; and the dotted red rectangle is the end state of the application.

This article describes the Dispatcher that controls the flow from one form to another and the common content of the forms themselves.
Table of Contents
- Introduction
- Table of Contents
- The Dispatcher
- The Forms
- Conclusion
- References
- Development Environment
- History
The symbol [toc] returns the reader to the top of the Table of Contents.
The Dispatcher [toc]
A finite state machine [^] is:
an abstract machine that can be in one of a finite number of states. The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition; this is called a transition. A particular finite state machine is defined by a list of its states, and the triggering condition for each transition.
The class ApplicationState enumerates the States used by the Dispatcher and maintains the Next_State property whose value will eventually become the value of the current_state.
For the set of forms, enumerated above, the Dispatcher States are:
public enum States
{
NOT_SPECIFIED, // no state yet specified
INITIALIZE, // any initialization
LOGIN, // accept user credentials
ORDER, // accept user order
REALIZE, // confirm order
// for each new Form add new state here
ERROR, // perform error processing
LOGOUT, // perform LOGOUT processing
TIMEDOUT, // perform TIMEOUT processing
FINISHED, // perform wrap-up processing
TERMINATE, // forces application exit
NUMBER_STATES = TERMINATE
}
Initially, the Dispatcher's current_state is set to ApplicationState.Next_State that, in turn, is initially set to States.INITIALIZE.
Each form that will be accessed by the Dispatcher is declared in the Dispatcher.
private static Form initialize = null;
private static Form login = null;
private static Form order = null;
private static Form realize = null;
Declaring the forms in the Dispatcher allows reuse of the forms, thus avoiding memory overflow. To extend the Dispatcher, additional forms are added and the ApplicationState class is modified to add the new form to the States enumeration.
At the conclusion of their processing, each form (e.g., initialize, login, order, realize, etc.) sets the value of ApplicationState.Next_State. In turn, the Dispatcher sets its current_state from the revised ApplicationState.Next_State.
The Dispatcher takes the following form:
// ************************************************ Dispatcher
public static void Dispatcher ( )
{
States current_state = ApplicationState.Next_State;
while ( current_state != States.TERMINATE )
{
switch ( current_state )
{
case States.INITIALIZE:
// create and open the initialize form
break;
case States.LOGIN:
// create and open the login form
break;
case States.ORDER:
// create and open the order form
break;
case States.REALIZE:
// create and open the realize form
break;
case States.TIMEDOUT:
case States.LOGOUT:
ApplicationState.Next_State = States.FINISHED;
break;
case States.ERROR:
MessageBox.Show (
String.Format (
@"Error in {0}",
current_state.ToString ( ) ) );
ApplicationState.Next_State = States.FINISHED;
break;
case States.TERMINATE:
break;
case States.FINISHED:
ApplicationState.Next_State = States.TERMINATE;
break;
default:
break;
}
current_state = ApplicationState.Next_State;
}
dispose_all_forms ( );
}
The dispose_all_forms helper method performs the task that its name implies.
The code for each of the form states takes the same form. Using the initialize Form as an exemplar:
switch ( current_state )
{
case States.INITIALIZE:
if ( initialize == null )
{
initialize = new Initialize ( );
}
if ( ( ( Initialize ) initialize ).initialize_form ( ) )
{
initialize.ShowDialog ( );
}
else
{
ApplicationState.Next_State = States.ERROR;
}
break;
For each form, the Dispatcher takes the following actions:
- Determines if the form exists (that is if the Form is null or not). If the Form is null, Dispatcher creates it.
- Invokes the initialize_form method of the Form.
- If the initialize_form method of the Form returns true, the Dispatcher invokes the Form's ShowDialog method, to display the Form as a modal dialog box; otherwise the Dispatcher will set ApplicationState.Next_State to States.ERROR.
- The Form performs its tasks and, upon completion, the form sets the value of ApplicationState.Next_State to the next state that the state machine is to process.
- The Dispatcher sets its current_state to the revised ApplicationState.Next_State.
The Forms [toc]
To use the finite state machine paradigm, there are some rather straight-forward restrictions imposed upon the content of the Forms:
- The namespace of all of the Forms must be known by the Dispatcher. In this article's software, the namespace is StateMachine across all Forms and classes.
-
The Form's class constructor should be limited in its tasks. If
the Visual Studio Designer is used, the constructor should only
contain the
InitializeComponent
invocation. If the Visual Studio Designer is not used, the
equivalent of the
InitializeComponent
invocation should occur. For the
initialize form, the
class constructor is:
// ************************************************ Initialize public Initialize ( ) { InitializeComponent ( ); }
-
A public method that returns a
bool must be known by the
Dispatcher. In all of the forms in this article's software, the
method is named
initialize_form. In
initialize it takes the
following form:
// ******************************************* initialize_form public bool initialize_form ( ) { bool successful = false; if ( !initialize_GUI_controls ( ) ) { successful = false; } else if ( !initialize_hardware ( ) ) { successful = false; } else if ( !credit_card_reader_present ( ) ) { successful = false; } else if ( !connected_to_printer ( ) ) { successful = false; } else { successful = true; } if ( !successful ) { close_form ( States.TERMINATE ); } return ( successful ); }
The contents of each of the initialize_form methods is dependent upon the purpose of the individual forms (i.e., initialize, login, order, or realize).
The helper method close_form, common to all forms, takes on the following form:// ************************************************ close_form void close_form ( States next_state ) { ApplicationState.Next_State = next_state; this.DialogResult = DialogResult.OK; this.Close ( ); }
Within the initialize form, close_form is also invoked by the continue and cancel button click event handlers:// **************************************** continue_BUT_Click void continue_BUT_Click ( object sender, EventArgs e ) { close_form ( States.LOGIN ); } // ****************************************** cancel_BUT_Click void cancel_BUT_Click ( object sender, EventArgs e ) { close_form ( States.FINISHED ); }
Note, it is within these event handlers that the next state of the finite state machine is determined.
Conclusion [toc]
This article has presented a paradigm to control multiple Forms using a finite state machine. The following figure is the flow for a real-world application that used this technique.

References [toc]
Development Environment [toc]
The software was developed in the following environment:
Microsoft Windows 7 Professional Service Pack 1 | |
Microsoft Visual Studio 2008 Professional | |
Microsoft .Net Framework Version 3.5 SP1 | |
Microsoft Visual C# 2008 |
History [toc]
03/25/2014 | Original Article |