AddThis Social Bookmark Button

Print

Writing C# Custom Events
Pages: 1, 2

Putting Theory Into Practice

The following example uses a custom control called WhiteBoard. The WhiteBoard class inherits the System.Windows.Forms.UserControl. As the name implies, this control acts like a small white board (10 characters x 10 characters) into which the user can type. In addition, there is a caret that blinks and indicates the current insertion point. The user can use the arrow keys to move the caret, and hence, the insertion point. The user can also use control keys, such as Ctrl+R, Ctrl+G, and Ctrl+B to change the background color of the control, and Ctrl+Alt+R, Ctrl+Alt+G, and Ctrl+Alt+B to change the text color. In this article, we will modify the control to add two events:



  • The LineChanged event, triggered when the y coordinate of the caret changes.

  • The ColumnChanged event, triggered when the x coordinate of the caret changes.

We will then capture these two events in a form to dynamically update a label that shows the caret's current position. As an overview, the WhiteBoard control is briefly explained here:

  • The board's two-dimensional array of characters contains the characters that the user has typed. At the class's constructor, all of the array elements are set to the space character.
  • The OnPaint method draws the characters into the control's Graphics object, line by line, character by character.
  • The caret is drawn by a separate thread called caretThread. This thread is started in the class's constructor and handles the ShowCaret method. The ShowCaret method contains an indefinite loop that make the caret flash on and off every 350 milliseconds.
  • caretThread is terminated when the control is disposed. We override the Dispose method.
  • The KeyPressed and ProcessDialogKey methods handle the pressing of the keyboard keys.
  • Note also that there are two properties, CaretX and CaretY (both with a capital C), that can be used to obtain and modify the value of the caretX and caretY fields, respectively. Notice that the field names start with a lower case c. All modification of these two fields is done through CaretX and CaretY, for reasons that will become clear later.

Now, let's see how we can add two events to the WhiteBoard control: LineChanged and ColumnChanged. We do so by following the steps in the previous sections.

1. Declare two delegates: LineEventHandler and ColumnEventHandler.


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

2. Define and provide implementations for LineEventArgs and ColumnEventArgs.


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;
      }
    }
  }

Both classes have a constructor that accepts two integers: the old value and the new value. Both classes also have read-only properties to obtain these integers. In LineEventArgs, the properties are OldLine and NewLine. In ColumnEventArgs, the properties are OldColumn and NewColumn.

3. Declare the LineChanged and ColumnChanged events in the control class.


public event LineEventHandler LineChanged;
public event ColumnEventHandler ColumnChanged;

4. In the WhiteBoard class, declare two protected virtual methods for the two events: OnLineChanged and OnColumnChanged. In OnLineChanged, you raise the LineChanged event. In OnColumnChanged, you raise the ColumnChanged event.


    protected virtual void OnLineChanged(LineEventArgs e)
    {
      LineChanged(this, e);
    }
    protected virtual void OnColumnChanged(ColumnEventArgs e)
    {
      ColumnChanged(this, e);
    }

5. Now, call the OnLineChanged and OnColumnChanged methods from inside of the WhiteBoard class. The LineChanged event is triggered when the y coordinate of the caret changes, i.e., when the CaretY property changes value. Similarly, the ColumnChanged event occurs when the CaretX property changes value. Since the caretY and caretX fields are only accessed through the CaretY and CaretX properties, we only raise events from these properties to guarantee that every change in CaretY raises the LineChanged event, and that every time the value of CaretX changes, the ColumnChanged event is triggered.


    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));
      }
    }

The complete code of the WhiteBoard control, delegates, and event argument classes are given in Listing 1.

Consuming Events

After you compile the WhiteBoard control, you can now use it in a form. An example of such as form is shown in Figure 1. Note there is a label that displays the line and column position of the caret. The text of the label changes every time you move the caret. The code for the form is given in Listing 2.

Figure 1: Consuming the events in a form

Listing 2: Using the custom events in the WhiteBoard control


using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace CustomEvent
{
  public class Form1 : System.Windows.Forms.Form
    {
      private CustomEvent.WhiteBoard whiteBoard;
      private System.ComponentModel.Container components = null;
      private Label label;
      private int column, line;

      public Form1()
      {
        InitializeComponent();
      }

      protected override void Dispose( bool disposing )
      {
        if( disposing )
        {
          if (components != null) 
          {
            components.Dispose();
          }
        }
        base.Dispose( disposing );
      }

    private void whiteBoard_ColumnChanged(Object sender, ColumnEventArgs e)
    {
      column = e.NewColumn;
      label.Text = "Line:" + line.ToString() + "    Column:" + column.ToString();  
    }
    private void whiteBoard_LineChanged(Object sender, LineEventArgs e)
    {
      line = e.NewLine;
      label.Text = "Line:" + line.ToString() + "    Column:" + column.ToString();  
    }
#region Windows Form Designer generated code
      private void InitializeComponent()
      {
        whiteBoard= new WhiteBoard();
        this.SuspendLayout();
        whiteBoard.Location = new Point(20,20);
        whiteBoard.Size = new Size(190, 220);
        whiteBoard.ColumnChanged += new 
ColumnEventHandler(whiteBoard_ColumnChanged); 
        whiteBoard.LineChanged += new LineEventHandler(whiteBoard_LineChanged); 
        
        label = new Label();
        this.label.Location = new Point(20, 250);
        this.label.Size = new Size(100, 20);
        
        this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
        this.ClientSize = new System.Drawing.Size(292, 273);
        
        this.Controls.AddRange(new System.Windows.Forms.Control[] {whiteBoard, label});
        this.Name = "Form1";
        this.Text = "Small Whiteboard";
        this.ResumeLayout(false);

      }
#endregion

      [STAThread]
      static void Main() 
      {
        Application.Run(new Form1());
      }
    }
  }

Budi Kurniawan is a senior J2EE architect and author.


Return to the .NET DevCenter.