Click here to Skip to main content
Click here to Skip to main content

Tic-Tac-Toe Windows Control

, 2 Aug 2002
Rate this:
Please Sign up or sign in to vote.
Working through the issues of encapsulating functionality in a Windows Control using the simple program Tic-Tac-Toe.

At some point when programming with VS.NET, you are going to need to encapsulate functionality in a Windows control. There are two ways to approach this task – quick and dirty vs. very complete and thorough. For regular business computing, the quick and dirty method is usually the road taken – and this is the approach we’ll use. 

The primary design goals for the control are:

  1. Place the functionality into a Windows Controls with a minimum of fuss.
  2. Concentrate on building from pre-existing controls whenever possible.
  3. Provide a simple interface to operate the class.
  4. Put the data and functionality as deep as possible to hide as many implementation details as possible.
  5. Include error handling.
  6. Provide visual cues when someone won.

The functionality we’ll embed in the control is the simple game of Tic-Tac-Toe. Some of the code was taken from my earlier article on developing a WinForms version of Tic-Tac-Toe.  The code taken from earlier article will not be discussed. If you see something that does not make sense, it is probably a good idea to take a look at this article.

Overall Architecture

The Tic-Tac-Toe control will use the .NET classes System.Windows.Forms.Button and System.Windows.Forms.UserControl to form the foundation of the control. 


If you are not familiar with UML, a good place to start is www.uml.org or visit your local bookstore. UML, in some form, is here to stay so it is pretty beneficial to learn at least the basics.

Starting the Project

To start the project, create a WinForms project/solution called TicTacToeWinControl. This part of the project will be used to test the control.

Next, add a Windows Control Library project to the TicTacToeWinControl solution called TicTacToeControl. The Tic-Tac-Toe control will be created in this project. Go into the code and delete everything between the curly braces of the class. All those elements are really nice and useful ... but we won’t need them.

Tic-Tac-Toe Utility Class

The first order of business is to create a utility class. This one has two types of components: 
  1. a changeable set of values for the regular and winning fonts/colors and 
  2. error condition processing. 

Under many circumstances the fonts and colors would be constants. But, in the future, it might be useful to be able to change the fonts – to scale for the screen size or for personal preferences. Two methods, NormalFontSize() and WinnerFontSize() are created to set the size of the fonts as an example -- although they will not be used in this program.

public class TicTacToeUtils
{
    static public Font normalFont = new Font("Microsoft Sans Serif", 
        24F,  System.Drawing.FontStyle.Regular, 
        System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));

    static public Font winnerFont = new Font("Microsoft Sans Serif", 
        32F, 
        System.Drawing.FontStyle.Italic & System.Drawing.FontStyle.Bold, 
        System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));

    static public Color normalBackColor = Color.LightGray;
    static public Color winnerBackColor = Color.LightCoral;
    static public Color allForeColor = Color.Black;

    static TicTacToeUtils()
    {
        _hasError = TicTacToeUtils.NoError;
    }

    static void NormalFontSize( float fontSize )
    {
        normalFont = new Font("Microsoft Sans Serif", fontSize,
                System.Drawing.FontStyle.Regular,
                System.Drawing.GraphicsUnit.Point, 
                ((System.Byte)(0)));
    }

    static void WinnerFontSize( float fontSize )
    {
        winnerFont = new Font("Microsoft Sans Serif", fontSize, 
          System.Drawing.FontStyle.Italic & System.Drawing.FontStyle.Bold,
          System.Drawing.GraphicsUnit.Point, 
          ((System.Byte)(0)));
    }

    //------------------------------------------------------
    // Somehow, when there is a error, this error condition
    // needs to be communicated across different event
    // handlers. Each handler for the program, should check
    // the HasError condition for true before doing anything.
    //--------------------------------------------------------
    static private int _hasError;
    static public bool HasError() { return (_hasError > 0 ); }
    static public void SetError(int errNo ) { _hasError = errNo; }
    static public int GetError() { return _hasError; }
    static public void ClearError() { _hasError = 0; }

    public const int NoError = 0;
    public const int Unknown = 1;
    public const int AlreadySelected = 2;
    public const int GameOver = 3;

    static private string [] _errorMsgs = {
                "No Known Error",
                "Unknown Error",
                "Tic-Tac-Toe square already has a value!",
                "Game Over! Select New to start another game"
                      };

    static public string ErrorMessage( int errNo )
    {
        if( errNo < _errorMsgs.Length )
            return _errorMsgs[errNo];
        else
            return "Unknown Error:" + errNo.ToString();
    }
} 
To record errors, an internal variable, _hasError, and a series of interfaces to it is used to record if an error occurred and what type of error it was. This is useful if there is more than one event handler.

The strategy for processing event handler errors is to set the error handler to NoError in the beginning of the first handler and to set the error when the error occurs. It is the responsibility of the next event handler to check the error to see if there is a problem -- and to alert the user to the error or not. If we choose to alert the user, then the message can be gotten with ErrorMessage().

An interesting detail is the static constructor. Static constructors do not have a return type or take any arguments. A static constructor will always be called – and will be called before any other constructors. It is most often used to initialize static variables or other variables that need values before the regular constructors are called.

Tic-Tac-Toe Square Control

Create a single square of the Tic-Tac-Toe board using the System.Windows.Forms.Button control. The functionality needed is:

  1. When clicked on and blank, will present an X or O. 
  2. If there is an X or O in the square and the button is clicked on, display an error message.
  3. Change the font and color to indicate the square is part of a winning combination.
  4. Set the button back to default values.

The System.Windows.Forms.Button will be extended to include the functionality we need. The code is below:

//-----------------------------------------------------------
// TicTacToe class is the class the controls the individual
// squares on the tic-tac-toe board. 
//-----------------------------------------------------------
public class TicTacToeControl : System.Windows.Forms.Button  
{
    public TicTacToeControl()
    {
        this.Normal();    // default is Normal size
        this.Click += new System.EventHandler(ClickHandler);
    }

    public void Normal()    // sets the text to the normal operations
    {
        this.Text = "";
        this.ForeColor = TicTacToeUtils.allForeColor;
        this.BackColor = TicTacToeUtils.normalBackColor;
        this.Font = TicTacToeUtils.normalFont;
    }

    public void Won()    // Hooorrrraaaay!...someone won.
    {
        this.BackColor = TicTacToeUtils.winnerBackColor;
        this.Font = TicTacToeUtils.winnerFont;
    }
    //----------------------------------------------------------------------
    // handler for click ... will be combined with the the caller's handler
    //----------------------------------------------------------------------
    public static void ClickHandler(object sender, System.EventArgs e)
    {
        TicTacToeUtils.ClearError();     
                                        
        // cast it to the right type
        TicTacToeControl tempButton = (TicTacToeControl)sender; 
        try
        {
            if( tempButton.Text != "" )    // if is it empty
            {
                throw new XYSquareControlException(TicTacToeUtils.AlreadySelected);
            }
        }
        catch (XYSquareControlException ) 
                 {
            MessageBox.Show(
                TicTacToeUtils.ErrorMessage(TicTacToeUtils.GetError()),
                "ERROR",
                MessageBoxButtons.OK);
                }
    }
}// end of class TicTacToeControl...
The only significant detail is the error handler XYSquareControl exception. This was created to trap any error in the Click event handler and set an error number that it can be checked in other Click event handlers. 
//------------------------------------------------
// Exception class for XY Squares
//------------------------------------------------
public class XYSquareControlException:Exception
{
    public XYSquareControlException():base() 
    {
        TicTacToeUtils.SetError( TicTacToeUtils.Unknown );
    }
    public XYSquareControlException(int errNo ):base()
    {
        TicTacToeUtils.SetError( errNo );
    }
} // end of class XYSquareControlException

Creating the TicTacToeGame Control

Now construct the 3x3 Tic-Tac-Toe control. From this control we need to:

  1. Place the squares evenly on the board – making sure they are uniform.
  2. Initialize the squares board to default settings.
  3. Handle resizing the Tic-Tac-Toe game.
  4. After each valid selection of X or Y, check for a winner.

The control’s base is inherited from System.Windows.Forms.UserControl. This class provides many of the building blocks for creating a custom control. The .NET documentation will fill you in on the details.

To initialize the board, the private method InitTicTacToe() is called. This routine sets each Tic-Tac-Toe square to normal and sets the two instance variables.

private void InitTicTacToe()    // put the game back to the start
{
    for(int i=0;i<_buttonArray.Length;i++)
        _buttonArray[i].Normal();

    this._isX = true; // is the current square to display an X
    this._isGameOver = false;
}

To display information on the screen, the paint event is used. Paint happens under two conditions: 1 ) when something covers/uncovers the Windows requiring the re-drawing of the windows and 2) manually to reflect programmatic changes in the Window’s content. The paint handler is below: 

private void PaintHandler(object sender, 
                          System.Windows.Forms.PaintEventArgs e)
{
    int locX;
    int locY;
    int arrayIndex = 0;

    int sizeX = this.Width / 3;    // button sizes are calculated by
    int sizeY = this.Height / 3;    // dividing the width & height by 3
    Size buttonSize = new Size(sizeX, sizeY);

    this.SuspendLayout();            // don't update screen

    for( int i = 0; i < 3 ; i++)        // start filling the top row first
        for( int k = 0; k < 3; k++ )    // march across...
        {
            locX = sizeX * k; 
            locY = sizeY * i;
            _buttonArray[arrayIndex].Location = new Point(locX, locY );
            _buttonArray[arrayIndex].Size = buttonSize;
            arrayIndex++;
        }
    this.ResumeLayout();
}

The paint handler resizes the squares to fully occupy the size of the control. By taking the Width and Size properties and dividing them by three, the squares can be evenly sized and distributed. Once the optimal size is determined, a Size object is created to represent the dimension of all the squares. The location for the buttons is calculated by multiplying the loop variables -- and these are used to create a Point().

Before starting to draw the buttons on the screen, the method SuspendLayout() is invoked to delay writing changes to the screen. In the loop, we walk through the array -- painting the squares on the screen. Once done, ResumeLayout() is called and the output is flushed to the screen. 

To trigger a manual redraw of the screen, the function Invalidate() is called. If called without any arguments, this redraws the complete window/control area. (Note, there is a way to increase the granularity of the areas to be repainted using Invalidate() – which can be another subject of another article.)  Once the Paint handler has been written, it becomes easy to handle the Resize event with a simple event handler that just calls the Invalidate() method to force a redraw (re-Paint) the control’s area. 

private void ReSizeHandler(object sender, System.EventArgs e)
{
    this.Invalidate(); // need to trigger Paint event to resize squares
}

The TicTacToeGame control extends each of the TicTacTocControl’s Click event. As this is added after the Click event handler is added, this is called after it. The code for the handler is below.

private void ClickHandler(object sender, System.EventArgs e)
{
    TicTacToeControl tempButton = (TicTacToeControl)sender;
            
    if( TicTacToeUtils.HasError())// check error handler in TicTacToeUtils
        return;

    try
    {
        if( this._isGameOver )
            throw new XYSquareControlException(TicTacToeUtils.GameOver);
    }
    catch (XYSquareControlException ) 
    {
        MessageBox.Show(
            TicTacToeUtils.ErrorMessage(TicTacToeUtils.GetError()),
            "ERROR",
            MessageBoxButtons.OK);
        return;
    }

    if( this._isX )    // put the character in the Text property
        tempButton.Text = "X";
    else
        tempButton.Text = "O";

    this._isX = !this._isX;    // prepare for next character

    // returns true if game over
    this._isGameOver = CheckAndProcessWinner(_buttonArray );  
    return;
}

The code for the CheckAndProcessWinner() method checks to see if there is a winner and if there is a winner the font and color changes according to winnerBackColor and winnerFont in the TicTacToeUtils. If there is a winner, CheckAndProcessWinner() returns true – otherwise false. This is discussed in more detail in the earlier article.

Adding the Control to the Toolbox

Once the TicTacToe control is successfully compiled, it can be added to the Toolbox. To do this:

  1. Go to the project tictactoeWinControl.

  2. Open the Windows Form.

  3. Right click on the Windows Forms top caption.

  4. Two tabs will appear, COM Components and .NET Framework Components. Select the .NET Framework Components tab.

  5. The following screen will appear.

  6. Select the Browse… button and find the dll in the …\TicTacToeWinControl\Bin\Debug directory. Select the TicTacToeControl.dll and click on the Open button.

  7. This will display the following screen. You should consider unchecking the old versions – just to be sure.

  8. After clicking on OK, the button will appear at the end of the Toolbox. If you want them to appear higher on the Tool Bar, it can be dragged upward.

Making the Tic-Tac-Toe Game

In the project tictactoeWinControl:
  1. Select and drag two TicTacToeGame controls onto the screen. 
  2. Select and drag to buttons under the TicTacToeGame controls and set the Text properties for each to New Game.

The screen should look like:

All that is required to play the two games is to call the NewGame() method of the TicTacToeGame control from the “Play Game” buttons. An example is below.

private void butPlayControl_Click(object sender, System.EventArgs e)
{
    ticTacToeGame2.NewGame();
}

There is still some work to do – like printing, automatic adjustment of the fonts, menus, configuration save/restore and automatic play (1 or 2 player) just to name a few. But the bells and whistles will have to wait…

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Grant Richard
Web Developer
United States United States
No Biography provided

Comments and Discussions

 
GeneralHi There Pinmemberflavio.rodriguez31-Aug-07 20:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    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 | Mobile
Web03 | 2.8.140709.1 | Last Updated 3 Aug 2002
Article Copyright 2002 by Grant Richard
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid