Listing 1: The WhiteBoard control with events


using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;

namespace CustomEvent
{

  public delegate void LineEventHandler(Object sender, LineEventArgs e);
  public delegate void ColumnEventHandler(Object sender, ColumnEventArgs e);

  public class LineEventArgs: EventArgs
  {
    private int oldValue, newValue;
    public LineEventArgs(int oldValue, int newValue)
    {
      this.oldValue = oldValue;
      this.newValue = newValue;
    }

    public int NewLine
    {
      get
      {
        return newValue;
      }
    }
    public int OldLine
    {
      get
      {
        return oldValue;
      }
    }
  }
  public class ColumnEventArgs: EventArgs
  {

    private int oldValue, newValue;
    public ColumnEventArgs(int oldValue, int newValue)
    {
      this.oldValue = oldValue;
      this.newValue = newValue;
    }

    public int NewColumn
    {
      get
      {
        return newValue;
      }
    }
    public int OldColumn
    {
      get
      {
        return oldValue;
      }
    }
  }

  public class WhiteBoard: UserControl
  {

    public event LineEventHandler LineChanged;
    protected virtual void OnLineChanged(LineEventArgs e)
    {
      LineChanged(this, e);
    }
    public event ColumnEventHandler ColumnChanged;
    protected virtual void OnColumnChanged(ColumnEventArgs e)
    {
      ColumnChanged(this, e);
    }
    
    private int rowCount = 10;
    private int columnCount = 10;
    private char[,] board;
    private int characterWidth = 10;
    private int characterHeight = 14;
    private int lineSpace = 3;
    public Color foreColor = Color.Black;
    private Thread caretThread;
    private bool caretVisible = true;
    private int caretX, caretY;
    private int penWidth = 2;
    private Pen pen;
    private bool keystrokeProcessed;
    
    public WhiteBoard()
    {
      board = new char[columnCount, rowCount];
      //initialize characters
      for (int i=0; i<columnCount; i++)
      {
        for (int j=0; j<rowCount; j++)
        {
          board[i, j] =' '; 
        }
      }
      pen = new Pen(foreColor, penWidth);
      this.BackColor = Color.White;
      this.KeyPress += new KeyPressEventHandler(KeyPressed);

      caretThread = new Thread(new ThreadStart(ShowCaret));
      caretThread.Start();
		}

    public int CaretX
    {
      get
      {
        return caretX;
      }
      set
      {
        int oldValue = caretX;
        caretX = value;
        if (oldValue != caretX)
          OnColumnChanged(new ColumnEventArgs(oldValue, caretX));
      }
    }

    public int CaretY
    {
      get
      {
        return caretY;
      }
      set
      {
        int oldValue = caretY;
        caretY = value;
        if (oldValue != caretY)
          OnLineChanged(new LineEventArgs(oldValue, caretY));
      }
    }

    void ShowCaret()
    {
      try 
      {
        while (true) 
        {
          this.Invalidate( new 
            Rectangle(CaretX * characterWidth, CaretY * (characterHeight + lineSpace),
            CaretX * characterWidth + 2 * penWidth, (CaretY +1) * (characterHeight + 
lineSpace)));
          this.Update();					
          Thread.Sleep(350);
          caretVisible = !caretVisible;
        }
      } 
      catch (Exception) 
      {
      }
    }
		
    protected override void OnPaint(PaintEventArgs e)
    {
      Graphics graphics = e.Graphics;
      Font font = new Font("Courier new", characterWidth);
      Brush brush = new SolidBrush(foreColor);
      for (int i=0; i<rowCount; i++) 
      {
        for (int j=0; j<columnCount; j++)
        {
          graphics.DrawString(board[i, j].ToString(), font, brush, 
            new Point(i*characterWidth, j*(lineSpace+characterHeight)));
        }
      }
      //draw caret here;
      if (caretVisible)
      {
        int x = CaretX * characterWidth;
        int y = CaretY * (lineSpace + characterHeight);
        graphics.DrawLine(pen, x, y, x, y + lineSpace + characterHeight);
      }
    }

    private void KeyPressed(object sender, KeyPressEventArgs e)
    {
      if (!keystrokeProcessed) 
      {
        char c = e.KeyChar;
        int i = (int) c;
        if (i==8)
        {
          if (CaretX==0)
          {
            CaretX = columnCount - 1;
            if (CaretY==0)
              CaretY = rowCount - 1;
            else
              CaretY--;
          }
          else
          {
            CaretX--;
          }
          board[CaretX, CaretY] = ' ';
        }
        else 
        {
          board[CaretX, CaretY] = c;
          if (CaretX==columnCount -1)
          {
            CaretX = 0;
            if (CaretY==rowCount -1)
              CaretY = 0;
            else
              CaretY++;
          }
          else
            CaretX++;
        }
        this.Invalidate();
        this.Update();
      }
    }

    protected override bool ProcessDialogKey(Keys keyData)
    {
      keystrokeProcessed = true;
      switch (keyData) 
      {
        case Keys.Down:
          if (CaretY==rowCount-1)
            CaretY = 0;
          else
            CaretY++;
          break;
        case Keys.Up:
          if (CaretY==0)
            CaretY = rowCount - 1;
          else
            CaretY--;
          break;
        case Keys.Left:
          if (CaretX==0)
          {
            CaretX = columnCount -1;
            if (CaretY==0)
              CaretY = rowCount - 1;
            else
              CaretY--;
          }
          else
            CaretX--;
          break;
        case Keys.Right:
          if (CaretX==columnCount-1) 
          {
            CaretX = 0;
            if (CaretY==rowCount-1)
              CaretY = 0;
            else
              CaretY++;
          }
          else
            CaretX++;
          break;
        case Keys.Control | Keys.R:
          this.BackColor = Color.Red;
          break;
        case Keys.Control | Keys.G:
          this.BackColor = Color.Green;
          break;
        case Keys.Control | Keys.B:
          this.BackColor = Color.Blue;
          break;
        case Keys.Control | Keys.Alt | Keys.R:
          foreColor = Color.Red;
          break;
        case Keys.Control | Keys.Alt | Keys.G:
          foreColor = Color.Green;
          break;
        case Keys.Control | Keys.Alt | Keys.B:
          foreColor = Color.Blue;
          break;
        case Keys.Escape:
          this.BackColor = Color.White;
          break;
        case Keys.Alt | Keys.F4:
          Application.Exit(); //uu... abrupt exit.
          break;
        case Keys.F1:
          MessageBox.Show("Help is on the way", "Message from ProcessDialogKey");
          break;
        default:
          if ((int)(Keys.Control & keyData) != 0) 
          {
            //The control key is pressed. Do something here if you want.
            return true;
          }
          else if ((int)(Keys.Alt & keyData) != 0) 
          {
            //The Alt key is pressed. Do something here if you want.
            return true;
          }
          else 
          {
            keystrokeProcessed = false; // let KeyPress event handler handle this keystroke.
          }
          break;
      }
      this.Invalidate();
      this.Update();
      return base.ProcessDialogKey(keyData);
    }

    protected override void Dispose(bool disposing)
    {
      if (disposing) {
        caretThread.Abort();
      }
      base.Dispose(disposing);
    }
  }
}