Click here to Skip to main content
12,293,588 members (61,700 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

9.6K views
559 downloads
21 bookmarked
Posted

Controlling Multiple Forms with a Finite State Machine

, 26 Mar 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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:

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.

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)

Share

About the Author

gggustafson
Software Developer (Senior)
United States United States
I started programming more than 42 years ago using AutoCoder and RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround between submitting a job for compilation and execution was about 3 hours. So much for the "good old days!" Today, I particularly enjoy programming real-time software. I consider myself capable in WinForms, Mobile Apps, and C# although there are occasions that I yearn to return to C and the Win32 API.

You may also be interested in...

Comments and Discussions

 
GeneralMy vote of 5 Pin
gicalle7527-Mar-14 2:41
membergicalle7527-Mar-14 2:41 
GeneralRe: My vote of 5 Pin
gggustafson27-Mar-14 10:20
professionalgggustafson27-Mar-14 10:20 
Generalvery good thoughts Pin
Southmountain26-Mar-14 6:14
memberSouthmountain26-Mar-14 6:14 
GeneralRe: very good thoughts Pin
gggustafson26-Mar-14 8:28
professionalgggustafson26-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.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160525.2 | Last Updated 26 Mar 2014
Article Copyright 2014 by gggustafson
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid