AddThis Social Bookmark Button

Print

C# Key Processing Techniques

by Budi Kurniawan
04/29/2002

The keys on a computer keyboard, unlike those in a conventional typewriter, fall into several categories:

  • Normal input characters, including alphanumeric characters and punctuation marks.
  • Function keys: F1 to F12.
  • Control keys: Control, Alt, Shift, the arrow keys, and others.

Processing the complete set of keys not only requires the detection of the pressing of individual key, but also the pressing of keys in combination. For this article, I have written two versions of a user control called WhiteBoard. The first version, given in Listing 1, captures keys in the first category; however, it cannot capture function and control keys. The second version, given in Listing 3, captures all keys.

Both versions are covered in the sections "Processing Characters" and "Processing All Keys." Note that in my testing with the current version of the .NET Framework SDK (version 1.0), the KeyDown event in System.Windows.Forms.Control is not triggered when the user presses an arrow key. However, the KeyDown event in System.Windows.Forms.Form is raised when an arrow key is pressed. The Form class extends the ContainerControl class, which extends the ScrollableControl class. The ScrollableControl class is a direct child class of the Control class. The class library reference in the .NET Framework SDK documentation does not state that the KeyDown event or the OnKeyDown method are overriden in any of ContainerControl, ScrollableControl, or Form. Therefore, the behavior of this event in both the Form and the Control classes must be the same. This leads to the following possibilities: either the documentation is not up to date or there is a bug in the class library.

Processing Characters

Related Reading

C# Essentials
By Ben Albahari, Peter Drayton, Brad Merrill

If the user presses a key on the keyboard when a control has focus, three events of the control are triggered. The three key events occur in the following order:

  1. KeyDown. Occurs when the user starts pressing the key, i.e., when the key is down.
  2. KeyPress. Occurs when a key is pressed, after the KeyDown event is triggered.
  3. KeyUp. Occurs when the user releases the key.

The easiest way to capture keyboard input from the user is to use the KeyPress event of a control. The event handler for this event receives a System.Windows.Forms.KeyPressEventArgs object containing two properties:

  • Handled. A boolean indicating whether the key has been handled.
  • KeyChar. A read-only property from which the corresponding character of the pressed key can be obtained.

Since the KeyChar property gives you the character of the key being pressed, displaying the character, for example, is very straightforward. However, some keys do not have visual representation and are not meant to be displayed. The backspace key, for instance, is normally used in a text-based control to delete the character to the left of the caret and move the caret back one character. In this case, you can simply convert the character into an integer and compare the integer with the ASCII value of the character.


char c = e.KeyChar;
int i = (int) c;

The backspace key will have an integer value of 8 and the carriage-return key 13. The use of the KeyPress event is illustrated in the WhiteBoard control displayed in a form in Figure 1.


Figure 1. The WhiteBoard control that captures characters.

The WhiteBoard control extends the System.Windows.Forms.UserControl class and its code is given in Listing 1.

As can be seen in Figure 1, the WhiteBoard control is a two-dimensional array of characters that has a visual interface. The two-dimensional array is represented by the variable board. private char[,] board;

The dimensions are indicated by the variables columnCount and rowCount.


board = new char[columnCount, rowCount];

Every time the OnPaint method of the control is invoked, the value of each element of the array is drawn using the DrawString method of the Graphics object of the control. The location of each character is determined by characterWidth, characterHeight, and lineSpace. The latter indicates the distance between two lines in pixels.


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

Like most decent text-based controls, our WhiteBoard control uses a caret to tell the user the location of the current character insertion point. That's right, a caret does not come free. You have to draw your own caret and make it blink (animate). In our WhiteBoard control, the location of the caret is determined by two integers: caretX and caretY. They indicate the horizontal and vertical coordinates in the visual area. Every time the user presses a key, the caret is moved forward by one character. When it reaches the last column of the last line, it will move back to the first column of the first line.

The animation of the caret is achieved by the use of the System.Threading.Thread class. Surprisingly, thanks to the classes in the System.Threading namespace, multi-threaded programming is relatively easy. The Thread class represents a thread in the program. In the WhiteBoard control, we use a thread called caretThread for displaying and animating the caret in the right position: private Thread caretThread;. When the control is created, caretThread must already be ready. Therefore, we start the caret thread in the class's constructor.


caretThread = new Thread(new ThreadStart(ShowCaret));
caretThread.Start();
In the above code, the first line constructs a Thread object and informs the thread that it is to handle the ShowCaret method. The second line starts the thread, i.e., starts executing the ShowCaret method. Before we look at the ShowCaret method, however, bear in mind that you are responsible for stopping and destroying the thread when it is no longer needed. Failure to do so will make the program unable to exit properly. In our WhiteBoard control, this is handled by overriding the control's Dispose method.

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

Therefore, when the control is disposed, the Thread class's Abort method will be called to terminate the thread. The ShowCaret method, as seen in Listing 1, employs an indefinite while loop that makes the thread execute the same piece of code. The code in this while loop is simple. It invalidates the part of the Graphics object that is occupied by the caret. The Update method is then called. This will invoke the OnPaint method, but the control will only redraw the area indicated by the rectangle passed to the Invalidate method. Whether or not the caret is visible is determined by the caretVisible variable. If it is true, the vertical line is drawn. Otherwise, nothing is drawn, making the caret invisible. The rate at which the caret blinks is determined by the value passed to the Sleep method of the Thread class. Here we use 350 (milliseconds). The caretVisible variable is also toggled to create the blinking effect.


      this.Invalidate( new 
            Rectangle(caretX * characterWidth, caretY * (characterHeight + lineSpace),
            caretX * characterWidth + 2 * penWidth, (caretY +1) * (characterHeight + 
            lineSpace)));
          this.Update();					
          Thread.Sleep(350);
          caretVisible = !caretVisible;
The caret itself is drawn when the OnPaint method is called:
      //draw caret here;
      if (caretVisible)
      {
        int x = caretX * characterWidth;
        int y = caretY * (lineSpace + characterHeight);
        graphics.DrawLine(pen, x, y, x, y + lineSpace + characterHeight);
      }

Pages: 1, 2

Next Pagearrow