Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Controlling Multiple Forms with a Finite State Machine

Rate me:
Please Sign up or sign in to vote.
4.70/5 (14 votes)
26 Mar 2014CPOL4 min read 22.9K   915   26   4
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.

Form Flow

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:

C#
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.

C#
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:

C#
// ************************************************ 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:

C#
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:
    C#
    // ************************************************ 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:
    C#
    // ******************************************* 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:
    C#
    // ************************************************ 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:
    C#
    // **************************************** 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.

Real World Example

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
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.

Comments and Discussions

 
GeneralMy vote of 5 Pin
gicalle7527-Mar-14 2:41
professionalgicalle7527-Mar-14 2:41 
GeneralRe: My vote of 5 Pin
gggustafson27-Mar-14 10:20
mvagggustafson27-Mar-14 10:20 
Generalvery good thoughts Pin
Southmountain26-Mar-14 6:14
Southmountain26-Mar-14 6:14 
GeneralRe: very good thoughts Pin
gggustafson26-Mar-14 8:28
mvagggustafson26-Mar-14 8:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.