您的位置:首页 > 产品设计 > UI/UE

C# Key Processing Techniques

2004-11-24 20:07 489 查看
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

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:

KeyDown. Occurs when the user starts pressing the key, i.e., when the key is down.
KeyPress. Occurs when a key is pressed, after the
KeyDown
event is triggered.
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];

[/code]
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);
}
Now, let's discuss the 
KeyPressed
method that is called every time a key is pressed. First, the [code]KeyPressed
method must be registered to become the handler of the
KeyPress
event. We do it in the class's constructor.
this.KeyPress += new KeyPressEventHandler(KeyPressed);

The handling of keys is not that complex. Basically, our simple WhiteBoard control accepts all alphanumeric characters and punctuation marks. The method also detects the keying of backspace (ASCII 8) and makes backspace function properly.
char c = e.KeyChar;
int i = (int) c;
if (i==8)
{
if (caretX==0)
{
caretX = columnCount - 1;
caretY--;
if (caretY<0)
caretY = rowCount - 1;
}
else
{
caretX--;
}
board[caretX, caretY] = ' ';

[/code]For non-backspace characters, the method moves the caret forward by one character.
board[caretX, caretY] = c;
caretX++;
if (caretX == columnCount)
{
caretX = 0;
caretY++;
if(caretY== rowCount)
caretY = 0;
}
}
It then invalidates the control so that the
Graphics
object is repainted. Calling the
Invalidate
method without an argument will repaint the whole area of the
Graphics
object.
this.Invalidate();
this.Update();
The code in Listing 2 is a form that uses the WhiteBoard control.
Listing 2. A form that uses the WhiteBoard control
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;

namespace KeyProcessor
{
public class Form1 : System.Windows.Forms.Form
{
private KeyProcessor.WhiteBoard whiteBoard;
private System.ComponentModel.Container components = null;

public Form1()
{
InitializeComponent();
}

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

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

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});
this.Name = "Form1";
this.Text = "Small Whiteboard";
this.ResumeLayout(false);

}
#endregion

[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
When you compile and run the code in Listing 2 for the first time, you'll probably get excited that so little code produces a text area with a caret that flashes on and off! After a while, however, you'll notice that our WhiteBoard control is not perfect. Here is a list of the control's imperfections. You can probably add more items to this list.
Pressing Ctrl+H fools the control into thinking that the backspace key has been pressed. In most text processing applications, Ctrl+H is used to display the Replace dialog box. The arrow keys don't move the caret. Pressing the Control key in combination with an alphanumeric character sends an ASCII character that has no visual representation (those with values less than 27). This will be displayed as a small box, as shown in Figure 2.



Figure 2. Pressing the control character and an alphanumeric
character at the same time results in a box.
So, you can see that
KeyPress
can only handle characters. While this limitation does not create a fuss for small and simple applications, most applications require the proper handling of all keys, not just alphanumeric and punctuation mark keys. The
KeyDown
event allows you to capture function keys, but not arrow keys. The
KeyUp
event lets you capture all keys, but this event is only triggered when the user releases the key, by which time it is probably too late to handle the key pressing. The next technique we can resort to is the
ProcessDialogKey
method, which enables us to capture all keys.

Processing All Keys

The
ProcessDialogKey
method in the
System.Windows.Forms.Control
class is called automatically when a key or a combination of keys on the keyboard is pressed. Unlike the
KeyPress
event,
ProcessDialogKey
can capture any key, including the Control keys. However, it does not give you the character associated with the key; it only tells you which key is being pressed. For example, it tells you the A key was pressed, but it doesn't tell you whether the character should be the capital or lower-case A. There is a way to check the character case, but this requires more code.
For a simpler solution,
ProcessDialogKey
can be used in conjunction with the
KeyPress
event. The
KeyPress
event is invoked after the
ProcessDialogKey
method is called. Therefore, you can use a flag to tell the
KeyPress
event handler whether or not it needs to handle a key press. This flag is controlled by the
ProcessDialogKey
method. If the key press is a combination of control keys and a character key, the
ProcessDialogKey
will handle it and reset the flag. On the other hand, if the key pressed is a character key, the
ProcessDialogKey
will set the flag and let the
KeyPress
event handler take care of the key press. When overriding the
ProcessDialogKey
method, you should return
true
when the key was handled and
false
otherwise. Normally, when your
ProcessDialogKey
method does not process the key, you call the
ProcessDialogKey
method of the base class and return whatever value is returned by the base class's
ProcessDialogKey
method. The
ProcessDialogKey
method receives one of the
System.Windows.Forms.Keys
enumeration values as its argument. The
Keys
enumeration allows a bitwise combination of its values. You should look up the .NET Framework class library reference for the list of values of the
Keys
enumeration. The argument sent to the
ProcessDialogKey
method depends on the key(s) being pressed. For instance, if the user pressed the A key, the method will receive
Keys.A
; if Ctrl+S is pressed, the value sent is the bitwise combination of the Control key and the S key. The down arrow sends
Keys.Down
, the up arrow
Keys.Up
, and the right and left arrows
Keys.Right
and
Keys.Left
respectively. The F1 sends
Keys.F1
and Alt+F4 sends the bitwise combination of
Keys.Alt
and
Keys.F4
. When the Shift key is pressed, the method receives
Keys.Shift
, enabling you to know whether an upper/lower case character is being sent by the user.
The modified version of the WhiteBoard control in Listing 3 overcomes the "flaws" in the code in Listing 1 by also handling control keys via the overriding of the
ProcessDialogKey
. It uses a flag called
keystrokeProcessed
to indicate whether or not the
KeyPress
event handler needs to handle the key press. The code in Listing 3 is similar to the code in Listing 1, except that we now override the
ProcessDialogKey
method, and that the
KeyPress
event handler is only executed if the key has not been handled by
ProcessDialogKey
.
The overriding
ProcessDialogKey
in Listing 3 captures the following keys: Arrow keys: move the caret. Ctrl+R, Ctrl+G, Ctrl+B: change the background color to red, green, and blue, respectively. Ctrl+Alt+R, Ctrl+Alt+G, Ctrl+Alt+B: change the text color to red, green, and blue, respectively. Escape: changes the background color back to white. Alt+F4: exits the application. F1: displays a message box.
You can use the code in Listing 2 to see the modified control in action. Figure 3 shows the control with a red foreground color.


Figure 3. The modified control that can handle all keys View Listing 3: Handling All Keys[/i]

Conclusion

Key processing is one of the most important tasks in Windows programming, used to capture and process user keyboard input. The
System.Windows.Forms.Control
triggers the
KeyDown
,
KeyPress
, and
KeyUp
events when a key is pressed; the easiest way to process a key press is by providing a handler for the
KeyPress
event. However, the
KeyPress
event only handles characters, and is useless when a control key or a combination of control and character keys are pressed. For this, the
ProcessDialogKey
method can be overriden to provide the handling of control keys. The
ProcessDialogKey
method does not give the character when a character key is pressed, therefore
ProcessDialogKey
can be used in conjunction with the
KeyPress
event for simple key press handling.
Budi Kurniawan is an IT consultant specializing in Internet and object-oriented programming, and has taught both Microsoft and Java technologies.
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息