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
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,
INITIALIZE,
LOGIN,
ORDER,
REALIZE,
ERROR,
LOGOUT,
TIMEDOUT,
FINISHED,
TERMINATE,
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:
public static void Dispatcher ( )
{
States current_state = ApplicationState.Next_State;
while ( current_state != States.TERMINATE )
{
switch ( current_state )
{
case States.INITIALIZE:
break;
case States.LOGIN:
break;
case States.ORDER:
break;
case States.REALIZE:
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.
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:
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:
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:
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:
void continue_BUT_Click ( object sender,
EventArgs e )
{
close_form ( States.LOGIN );
}
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
|
03/25/2014
|
| Original Article
|
In 1964, I was in the US Coast Guard when I wrote
my first program. It was written in RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround was about 3 hours. So much for the "good old days!"
In 1970, when assigned to Washington DC, I started my MS in Mechanical Engineering. I specialized in Transportation. Untold hours in statistical theory and practice were required, forcing me to use the university computer and learn the FORTRAN language, still using punched cards!
In 1973, I was employed by the Norfolk VA Police Department as a crime analyst for the
High Intensity Target program. There, I was still using punched cards!
In 1973, I joined Computer Sciences Corporation (CSC). There, for the first time, I was introduced to a terminal with the ability to edit, compile, link, and test my programs on-line. CSC also gave me the opportunity to discuss technical issues with some of the brightest minds I've encountered during my career.
In 1975, I moved to San Diego to head up an IR&D project,
BIODAB. I returned to school (UCSD) and took up Software Engineering at the graduate level. After BIODAB, I headed up a team that fixed a
stalled project. I then headed up one of the two most satisfying projects of my career, the
Automated Flight Operations Center at Ft. Irwin, CA.
I left Anteon Corporation (the successor to CSC on a major contract) and moved to Pensacola, FL. For a small company I built their firewall, given free to the company's customers. An opportunity to build an
air traffic controller trainer arose. This was the other most satisfying project of my career.
Today, I consider myself capable.