Contents
Introduction
This example is based on the program TicTac from the book Programming Windows
95 with MFC by Jeff Prosise. There is a newer edition called
Programming
Windows With MFC [^].I chose this example to learn how to handle mouse events
in C# and .NET.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace TicTac
{
///
/// Summary description for Form1.
///
public class FTicTac : System.Windows.Forms.Form
{
private int[] m_nGameGrid;
private int m_nNextChar;
private Rectangle[] m_rcSquares;
private const int EX = 1;
private const int OH = 2;
private bool m_bDoubleClick = false;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
public FTicTac()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
m_nNextChar = EX;
m_nGameGrid = new int[9];
m_rcSquares = new Rectangle[9];
Size size = new Size( 96, 96 );
for( int j = 0 ; j < 9 ; j++ )
{
m_rcSquares[j].Size = size;
}
m_rcSquares[0].Offset( 16, 16 );
m_rcSquares[1].Offset( 128, 16 );
m_rcSquares[2].Offset( 240, 16 );
m_rcSquares[3].Offset( 16, 128 );
m_rcSquares[4].Offset( 128, 128 );
m_rcSquares[5].Offset( 240, 128 );
m_rcSquares[6].Offset( 16, 240 );
m_rcSquares[7].Offset( 128, 240 );
m_rcSquares[8].Offset( 240, 240 );
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
//
// FTicTac
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Name = "FTicTac";
this.Text = "Tic Tac Toe";
}
#endregion
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
Application.Run(new FTicTac());
}
protected override void OnMouseDown( MouseEventArgs e )
{
switch( e.Button )
{
case MouseButtons.Left:
OnLButtonDown( new Point( e.X, e.Y ) );
break;
case MouseButtons.Right:
OnRButtonDown( new Point( e.X, e.Y ) );
break;
}
base.OnMouseDown( e );
}
private void OnLButtonDown( Point p )
{
if( m_bDoubleClick )
{
m_bDoubleClick = false;
return;
}
if( m_nNextChar != EX )
{
return;
}
int nPos = GetRectID( p );
if( nPos == -1 )
{
return;
}
if( m_nGameGrid[nPos] != 0 )
{
return;
}
m_nNextChar = OH;
m_nGameGrid[nPos] = EX;
Graphics g = this.CreateGraphics();
DrawX( g, nPos );
g.Dispose();
CheckForGameOver();
}
private void OnRButtonDown( Point p )
{
if( m_bDoubleClick )
{
m_bDoubleClick = false;
return;
}
if( m_nNextChar != OH )
{
return;
}
int nPos = GetRectID( p );
if( nPos == -1 )
{
return;
}
if( m_nGameGrid[nPos] != 0 )
{
return;
}
m_nNextChar = EX;
m_nGameGrid[nPos] = OH;
Graphics g = this.CreateGraphics();
DrawO( g, nPos );
g.Dispose();
CheckForGameOver();
}
protected override void OnDoubleClick( EventArgs e )
{
m_bDoubleClick = true;
ResetGame();
base.OnDoubleClick( e );
}
protected override void OnPaint( PaintEventArgs pe )
{
Graphics g = pe.Graphics;
DrawBoard( g );
}
private int GetRectID( Point p )
{
for( int i = 0 ; i < 9 ; i++ )
{
if( m_rcSquares[i].Contains( p ) )
{
return( i );
}
}
return( -1 );
}
private void DrawBoard( Graphics g )
{
Pen myPen = new Pen( Color.Black, 16 );
g.DrawLine( myPen, 120, 16, 120, 336 );
g.DrawLine( myPen, 232, 16, 232, 336 );
g.DrawLine( myPen, 16, 120, 336, 120 );
g.DrawLine( myPen, 16, 232, 336, 232 );
for( int i = 0 ; i < 9 ; i++ )
{
if( m_nGameGrid[i] == EX )
{
DrawX( g, i );
}
else if( m_nGameGrid[i] == OH )
{
DrawO( g, i );
}
}
myPen.Dispose();
}
private void DrawX( Graphics g, int nPos )
{
Pen myPen = new Pen( Color.Red, 16 );
g.DrawLine( myPen,
m_rcSquares[nPos].Left+16, m_rcSquares[nPos].Top+16,
m_rcSquares[nPos].Right-16, m_rcSquares[nPos].Bottom-16 );
g.DrawLine( myPen,
m_rcSquares[nPos].Left+16, m_rcSquares[nPos].Bottom-16,
m_rcSquares[nPos].Right-16, m_rcSquares[nPos].Top+16 );
myPen.Dispose();
}
private void DrawO( Graphics g, int nPos )
{
Pen myPen = new Pen( Color.Blue, 16 );
Rectangle rect = new Rectangle();
rect = m_rcSquares[nPos];
rect.Inflate( -16, -16 );
g.DrawEllipse( myPen, rect );
myPen.Dispose();
}
private void ResetGame()
{
m_nNextChar = EX;
for( int j = 0 ; j < m_nGameGrid.Length ; j++ )
{
m_nGameGrid[j] = 0;
}
this.Invalidate();
}
private void CheckForGameOver()
{
int nWinner = IsWinner();
switch( nWinner )
{
case 0:
if( IsDraw() )
{
MessageBox.Show( "It's a draw!", "Tic Tac Toe" );
ResetGame();
}
break;
case EX:
MessageBox.Show( "X Wins!", "Tic Tac Toe" );
ResetGame();
break;
case OH:
MessageBox.Show( "O Wins!", "Tic Tac Toe" );
ResetGame();
break;
}
}
private int IsWinner()
{
int[,] nPattern =
{
{ 0, 1, 2 },
{ 3, 4, 5 },
{ 6, 7, 8 },
{ 0, 3, 6 },
{ 1, 4, 7 },
{ 2, 5, 8 },
{ 0, 4, 8 },
{ 2, 4, 6 }
};
for( int i = 0 ; i < 8 ; i++ )
{
if( ( m_nGameGrid[nPattern[i,0]]== EX )
&& ( m_nGameGrid[nPattern[i,1]] == EX )
&& ( m_nGameGrid[nPattern[i,2]] == EX ) )
{
return( EX );
}
if( ( m_nGameGrid[nPattern[i,0]]== OH )
&& ( m_nGameGrid[nPattern[i,1]] == OH )
&& ( m_nGameGrid[nPattern[i,2]] == OH ) )
{
return( OH );
}
}
return( 0 );
}
private bool IsDraw()
{
for( int i = 0 ; i < 9 ; i++ )
{
if( m_nGameGrid[i] == 0 )
{
return( false );
}
}
return( true );
}
}
}
The properties for the Form which appear in InitializeComponent() were
set using the Properties Window. When modifying these, or other properties,
use the Properties Window instead of editing the code.
The OnMouseDown() event occurs twice during a double click, in addition
to OnDoubleClick(). The second OnMouseDown() occurs after
OnDoubleClick(), so I set a flag to cause the OnLButtonDown() and
OnRButtonDown() functions to return immediately. This code could be moved
to OnMouseDown(), but needs to be done so as not to skip the base.OnMouseDown(
e ) call.
To zero my array in the ResetGame() function, I tried m_nGameGrid.Initialize,
but this did not work. Checking the documentation, I found this:
CAUTION This method can only be used on value
types that have constructors, and value types that are native to C# do not have
constructors.
If you want to trigger a call to OnPaint() use this.Invalidate().
To draw outside the OnPaint() function, obtain a graphics object using Graphics
g = this.CreateGraphics().
Top 