In this section, you will see examples of events in use. In the first example, you will use keyboard events to capture keystrokes, showing what information is available about each keystroke. The next example will use keystroke information and the Validating event to control and validate the contents entered into a text box.
It is often useful or necessary to capture keystrokes and then take action based on the details related to that keystroke. For example, you may want to disallow certain characters or convert all lowercase characters to uppercase. Keyboard events provide access to this type of functionality.
The three events listed in Table 4-5 are raised when the user presses a key on the keyboard.
Table 4-5. Key Events for all controls
Event |
Event data |
Description |
---|---|---|
KeyDown |
KeyEventArgs |
Raised when a key is pressed. The KeyDown event occurs prior to the KeyPress event. |
KeyPress |
KeyPressEventArgs |
Raised when a character generating key is pressed. The KeyPress event occurs after the KeyDown event and before the KeyUp event. |
KeyUp |
KeyEventArgs |
Raised when a key is released. |
The KeyDown and KeyPress events may seem somewhat redundant, but they fire at different points in the keyboard event stream and contain different information in the EventArgs object.
The KeyEventArgs event data associated with the KeyDown and KeyUp events provides low-level information about the keystroke, listed in Table 4-6. This information allows you to determine, for example, if an upper- or lowercase character was pressed. It also tells you if any modifier keys (Alt, Ctrl, or Shift) were pressed and in which combination. (You will also get a KeyDown and a KeyUp event if a modifier key is pressed and released on its own.)
Table 4-6. KeyEventArgs properties (KeyDown and KeyUp)
Property |
Data type |
Description |
---|---|---|
Alt |
Boolean |
Read-only value indicating if the Alt key was pressed.
|
Control |
Boolean |
Read-only value indicating if the Ctrl key was pressed.
|
Shift |
Boolean |
Read-only value indicating if the Shift key was pressed.
|
Modifiers |
Keys |
Read-only flags indicating the combination of modifier keys (Alt, Ctrl, Shift) pressed. Modifier keys can be combined using the bitwise OR operator. |
Handled |
Boolean |
Value indicating if the event was handled. |
KeyCode |
Keys |
Read-only value containing the key code for the key pressed. Typical values include the A key, Alt, and BACK (backspace). |
KeyData |
Keys |
Read-only value containing the key code for the key pressed, combined with modifier flags to indicate combination of modifier keys (Alt, Ctrl, Shift). |
KeyValue |
integer |
Key code property expressed as a read-only integer. |
Tip
The state of the modifier keys can also be retrieved from the read-only Control.ModifierKeys property. This property is static in C# and shared in VB.NET. Like the Modifiers KeyEventArgs property, it is of type Keys.
The Modifiers, KeyCode and KeyData properties are of type Keys. The Keys enumeration, listed in Appendix A, is comprised of constants identifying all the possible keys on a keyboard. The decimal key code value in Appendix A corresponds to the virtual-key codes familiar to Windows programmers.
The KeyPress event exposes two properties contained in KeyPressEventArgs, listed in Table 4-7. The KeyChar property is used to retrieve the composed ASCII character. In other words, if an uppercase character is pressed, the KeyChar property tells you that directly, as opposed to telling you a character was pressed in combination with the Shift key.
Table 4-7. KeyPressEventArgs properties (KeyPress)
Property |
Description |
---|---|
Handled |
Boolean value indicating if the event was handled.
|
KeyChar |
Read-only value of type char containing the composed ASCII character. |
In the next example, you will create a simple Windows application with a single-line TextBox for entering keystrokes. A larger multiline TextBox will display the keystroke events and event argument properties so that you can see what is going on. Two Labels will simultaneously display the character in both upper- and lowercase, irrespective of how it was entered. Finally, a Reset button will clear all fields. During the course of the example, you will also see how to translate keystrokes from one character to another.
Open Visual Studio .NET and create a new project. Call it KeyEvents. (Since both C# and VB.NET examples are shown here, the examples will be saved as csKeyEvents and vbKeyEvents.)
Drag all the controls listed in Table 4-8 onto the form. Set the properties of the form and the controls to the values shown in Table 4-8. When done, the form should look something like Figure 4-7.
Table 4-8. KeyEvents controls
Control |
Name |
Property |
Value |
---|---|---|---|
Form |
Form1 |
Size |
425,320 |
Text |
Key Event Demonstrator | ||
TextBox |
txtInput |
Location |
8,8 |
Multiline |
False | ||
Size |
100,20 | ||
Text |
<blank> | ||
TextBox |
txtMsg |
Location |
8,40 |
MultiLine |
True | ||
ScrollBars |
Vertical | ||
Size |
304,232 | ||
TabStop |
False | ||
Text |
<blank> | ||
Button |
btnReset |
Location |
328,8 |
Size |
75,23 | ||
Text |
Reset | ||
Label |
label1 |
Location |
320,104 |
Size |
40,16 | ||
Text |
Lower: | ||
Label |
label2 |
Location |
320,56 |
Size |
40,16 | ||
Text |
Upper: | ||
Label |
lblUpper |
BorderStyle |
Fixed3D |
Location |
368,56 | ||
Size |
32,23 | ||
Text |
<blank> | ||
Label |
lblLower |
BorderStyle |
Fixed3D |
Location |
368,104 | ||
Size |
32,23 | ||
Text |
<blank> |
The Reset button will clear the Text properties of the TextBoxes and Labels. To implement this functionality, add an event handler for the Reset button. The easiest way to do this in either C# or VB.NET is to double-click on the control. Alternatively, you could use any of the language-specific techniques described earlier in this chapter. In any case, this will bring up a code window with an empty skeleton for the btnReset_Click event in place and the cursor placed for code entry.
Add the highlighted lines of code shown in Example 4-6 for C# and in Example 4-7 for VB.NET to the event handler skeletons in the code window.
You may notice the variable strMsg underlined in the Visual Studio .NET code window. It is underlined because the editor recognizes that this variable name has not yet been declared. You must declare strMsg as a member variable of the Form1 class so that it is visible to all of the methods of the class. To do this, add the appropriate line of code inside the Form1 class declaration:
private string strMsg = "";
Dim strMsg As String = ""
Now you will implement an event handler for the KeyDown event for the TextBox named txtInput. Do not double-click on the TextBox control, or an empty code skeleton will be inserted for the TextChanged event, which is the default event for the TextBox control.
Instead, use the techniques described previously in this chapter to insert a code skeleton for a nondefault event, in this case the KeyDown event for txtInput. In C#, highlight the control in the design window, and then click on the Events icon (
) in the Properties window. Scroll down to the KeyDown event, highlight the event, and press Enter. In VB.NET, use the drop-down lists at the top of the code window. In the left drop-down, select the control: txtInput. In the right drop-down, select KeyDown.
To implement the KeyDown event handler, add the highlighted code shown in Example 4-8 (for C#) or in Example 4-9 (for VB.NET) to the empty code skeletons. The KeyDown event handler will get the character from the KeyEventArgs event argument, extract various properties from the event argument, and then append that information to the TextBox txtMsg.
Example 4-8. txtInput KeyDown event in C#
private void txtInput_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { txtMsg.AppendText("KeyDown event." + "\r\n"); txtMsg.AppendText("\t" + "KeyCode name: " + e.KeyCode + "\r\n"); txtMsg.AppendText("\t" + "KeyCode key code: " + ((int)e.KeyCode) + "\r\n"); txtMsg.AppendText("\t" + "KeyData name: " + e.KeyData + "\r\n"); txtMsg.AppendText("\t" + "KeyData key code: " + ((int)e.KeyData) + "\r\n"); txtMsg.AppendText("\t" + "KeyValue: " + e.KeyValue + "\r\n"); txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n"); txtMsg.AppendText("\r\n"); }
Example 4-9. txtInput KeyDown event in VB.NET
Private Sub txtInput_KeyDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyEventArgs) _ Handles txtInput.KeyDown txtMsg.AppendText("KeyDown event." + vbCrLf) txtMsg.AppendText(vbTab + "KeyCode name: " + e.KeyCode.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyCode key code: " + _ CInt(e.KeyCode).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "KeyData name: " + e.KeyData.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyData key code: " + _ CInt(e.KeyData).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "KeyValue: " + e.KeyValue.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "Handled: " + e.Handled.ToString( ) + vbCrLf) txtMsg.AppendText(vbCrLf) End Sub
Run the application, make certain the input TextBox has focus, and enter an uppercase G (i.e., a shifted G). The result is shown in Figure 4-8.
Figure 4-8 shows that two KeyDown events were handled. The first was the pressed Shift key; the second was the letter G. The first two lines of data displayed for each event show the contents of the KeyEventArgs.KeyCode property. This is accomplished with the following two lines of code:
txtMsg.AppendText("\t" + "KeyCode name: " + e.KeyCode + "\r\n"); txtMsg.AppendText("\t" + "KeyCode key code: " + ((int)e.KeyCode) + "\r\n");
txtMsg.AppendText(vbTab + "KeyCode name: " + e.KeyCode.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyCode key code: " + _ CInt(e.KeyCode).ToString( ) + vbCrLf)
The object e refers to the instance of KeyEventArgs passed in as one of the method arguments. It contains the properties listed in Table 4-6. The KeyCode property contains a member of the Keys enumeration (listed in Appendix A) that identifies which key generated the KeyDown event.
e.KeyCode contains the name of the key. In VB.NET, the ToString( ) method must be used to include it as part of a string. That is not necessary in C#, although it would not do any harm.
Casting e.KeyCode to an integer returns the
KeyCode
key code, which corresponds to the
virtual-key code familiar to Windows programmers, which itself
corresponds (for the lower 127 characters) to the decimal ASCII value
for the key. (The ASCII characters are listed in
Appendix A.) The cast is done in C# using the cast
operator (( )) and in VB.NET using the CInt function.
There is another significant difference between the two languages here. The C# version embeds tab characters and new lines using escape sequences in string literals, while the VB.NET version uses VB.NET constants for the purpose. The commonly used VB.NET constants and their C# equivalent are listed in Table 4-9.
Table 4-9. Commonly used VB.NET constants and C# escape sequences
VB.NET constant |
C# escape sequence |
KeyCode value (decimal) |
Meaning |
---|---|---|---|
vbCr |
\r |
13 |
Carriage return |
vbCrLf |
\r\n |
13 & 10 |
Carriage return/line-feed combination |
vbFormFeed |
\f |
12 |
Form feed |
vbLf |
\n |
10 |
Line feed (new line) |
vbTab |
\t |
9 |
Tab |
The next two lines displayed in the output report on the KeyEventArgs.KeyData property. This is accomplished with the following lines of code:
txtMsg.AppendText("\t" + "KeyData name: " + e.KeyData + "\r\n"); txtMsg.AppendText("\t" + "KeyData key code: " + ((int)e.KeyData) + "\r\n");
txtMsg.AppendText(vbTab + "KeyData name: " + e.KeyData.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyData key code: " + _ CInt(e.KeyData).ToString( ) + vbCrLf)
The KeyData property returns the same information as the KeyCode property combined with flags to indicate which modifier keys were pressed, if any. In this example, the ShiftKey was pressed in combination with the Shift modifier key (that does seem redundant since they are the same key) and the G key was pressed, also in combination with the Shift modifier key.
The next line reports the value of the KeyValue property. This is the key code corresponding to the key pressed. It is redundant with the KeyCode:
txtMsg.AppendText("\t" + "KeyValue: " + e.KeyValue + "\r\n");
txtMsg.AppendText(vbTab + "KeyValue: " + e.KeyValue.ToString( ) + _ vbCrLf)
The final line displayed in the KeyDown event handler tells the
status of the Handled property, which is false
until specifically set otherwise:
txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n");
txtMsg.AppendText(vbTab + "Handled: " + e.Handled.ToString( ) + vbCrLf)
Looking ahead, the KeyUp and the KeyDown events both use the same event argument, KeyEventArgs, so it is reasonable that both event handlers will want to display the same information. To do this, abstract out the contents of the event handler into a helper method, passing the event argument in, and then call the helper method in the event handler. This process is shown in Example 4-10 for C# and in Example 4-11 for VB.NET.
Example 4-10. Handling KeyDown and KeyUp with helper method in C#
private void KeyMsgBox(string str, KeyEventArgs e) { txtMsg.AppendText(str + " event." + "\r\n"); txtMsg.AppendText("\t" + "KeyCode name: " + e.KeyCode + "\r\n"); txtMsg.AppendText("\t" + "KeyCode key code: " + ((int)e.KeyCode) + "\r\n"); txtMsg.AppendText("\t" + "KeyData name: " + e.KeyData + "\r\n"); txtMsg.AppendText("\t" + "KeyData key code: " + ((int)e.KeyData) + "\r\n"); txtMsg.AppendText("\t" + "KeyValue: " + e.KeyValue + "\r\n"); txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n"); txtMsg.AppendText("\r\n"); } private void txtInput_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) KeyMsgBox("KeyDown", e);
Example 4-11. Handling KeyUp and KeyDown with helper method in VB.NET
Private Sub KeyMsgBox(ByVal str As String, ByVal e As KeyEventArgs) txtMsg.AppendText(str + " event." + vbCrLf) txtMsg.AppendText(vbTab + "KeyCode name: " + e.KeyCode.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyCode key code: " + _ CInt(e.KeyCode).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "KeyData name: " + e.KeyData.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "KeyData key code: " + _ CInt(e.KeyData).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "KeyValue: " + e.KeyValue.ToString( ) + _ vbCrLf) txtMsg.AppendText(vbTab + "Handled: " + e.Handled.ToString( ) + vbCrLf) txtMsg.AppendText(vbCrLf) End Sub Private Sub txtInput_KeyDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyEventArgs) _ Handles txtInput.KeyDown KeyMsgBox("KeyDown", e) End Sub
The helper method is called KeyMsgBox. It takes two arguments: a string which should contain the name of the event and the instance of KeyEventArgs. The first argument is used in the first line in the method to display which event is being handled. e is used just as it was in the actual event handler method, described above.
The call to the helper method is simple; it involves passing in the name of the event and event argument:
KeyMsgBox("KeyDown", e);
KeyMsgBox("KeyDown", e)
Now that you have the KeyDown event handler implemented with a helper method to do the work, it is very simple to implement the KeyUp event handler in a similar fashion because both events use the same KeyEventArgs event argument. Again, remember not to double-click on the txtInput control, since that will implement the default event, which is not what you want here. Instead, use the techniques described above for the language you are using. The KeyUp event handler is shown implemented in C# in Example 4-12 and in VB.NET in Example 4-13.
When the application is run with the implemented KeyUp event handler and an uppercase G is again entered in the TextBox, you will get the results shown in Figure 4-9 (scrolling down to the bottom half of the displayed text). The two KeyDown events, for the Shift key and for the G key, are the same as seen previously in Figure 4-8. They are followed by the KeyUp event for the G key, and followed by the KeyUp event for the Shift key. The event data for the KeyUp event is identical to the event data for the KeyDown event.
The KeyDown and KeyUp events provide a lot of information, but often you really care about the ASCII value of the keystroke—i.e., how the operating system interprets the keystroke, not what key was actually pressed. For example, the G key will result in the same Keys enumeration of G, with a key code value of 71, irrespective of whether the Shift key was pressed (to produce an uppercase G) or not (to produce a lowercase g). To determine the case, you need to process additional properties. Similarly, the number 5 along the top of the keyboard will return a Keys enumeration of D5 with a key code value of 53, while the 5 on the numeric keypad will return a Keys enumeration of NumPad5 with a key code value of 101. In many applications, you won't care which key was pressed, you just want the ASCII value for the number 5, which is 53.
The KeyPress event provides exactly this sort of information. In addition, you can use the KeyPressEventArgs.Handled property to suppress a keystroke from being processed by the operating system. This will be demonstrated later.
To implement the KeyPress event handler, use the described techniques to add a KeyPress code skeleton to the ongoing example. Add the highlighted code shown in Example 4-14 (for C#) or in Example 4-15 (for VB.NET) to the empty code skeletons. This event handler will get the character from the KeyPressEventArgs event argument, and then append various pieces of information about the character to a string displayed in the txtMsg TextBox. It also populates the lblUpper label with an uppercase version of the character and lblLower label with a lowercase version.
Example 4-14. txtInput KeyPress event handler code in C#
private void txtInput_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { char keyChar; keyChar = e.KeyChar; txtMsg.AppendText("KeyPress event." + "\r\n"); txtMsg.AppendText("\t" + "KeyChar: " + keyChar + "\r\n"); txtMsg.AppendText("\t" + "KeyChar Code: " + (int)keyChar + "\r\n"); txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n"); txtMsg.AppendText("\r\n"); // Fill in the Upper and Lower labels lblUpper.Text = keyChar.ToString( ).ToUpper( ); lblLower.Text = keyChar.ToString( ).ToLower( ); }
Example 4-15. txtInput KeyPress event handler code in VB.NET
Private Sub txtInput_KeyPress(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) _ Handles txtInput.KeyPress Dim keyChar As Char keyChar = e.KeyChar txtMsg.AppendText("KeyPress event." + vbCrLf) txtMsg.AppendText(vbTab + "KeyChar: " + keyChar + vbCrLf) txtMsg.AppendText(vbTab + "KeyChar Code: " + _ AscW(keyChar).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "Handled: " + e.Handled.ToString( ) + vbCrLf) txtMsg.AppendText(vbCrLf) ' Fill in the Upper and Lower labels lblUpper.Text = keyChar.ToString( ).ToUpper( ) lblLower.Text = keyChar.ToString( ).ToLower( ) End Sub
Running the application now and entering the letter
G
without the Shift key produces the results shown
in Figure 4-10. Notice that the KeyPress information
returns a lowercase g
and a key code of 103,
rather than the key code of 71 returned by the KeyDown event. 103 is
the ASCII value for lowercase g
while 71 is the
ASCII value for uppercase G
.
The first two lines in the event handler get the character entered at the keyboard from the KeyPressEventArgs event argument and assign it to a variable keyChar, declared as type char, since the KeyPressEventArgs.KeyChar property is of type char (i.e., it is a Unicode character).
The next several lines in the event handler use the KeyPressEventArgs.KeyChar property to retrieve the composed ASCII character, i.e., already taking into account modifier keys. Both the character name and integer value are displayed:
txtMsg.AppendText("\t" + "KeyChar: " + keyChar + "\r\n"); txtMsg.AppendText("\t" + "KeyChar Code: " + (int)keyChar + "\r\n");
txtMsg.AppendText(vbTab + "KeyChar: " + keyChar + vbCrLf) txtMsg.AppendText(vbTab + "KeyChar Code: " + _ AscW(keyChar).ToString( ) + vbCrLf)
Since the KeyChar property is of type char, there is no need to use the ToString method in either language to concatenate it into a string. The character code value, on the other hand, is an integer and must be cast as such, and then converted to a string using the ToString method.
Note
Objects of type char can implicitly convert to a string, since there is no loss of data in such a conversion. This is true even in VB.NET with the type checking switch on (Option Strict On). You can not implicitly convert from string to char, as information would be lost.
To declare a literal character in C#, enclose it in single quotes:
char myChar = 'A';
In VB.NET, you append the letter c
, as in:
Dim myChar as Char = "A"c
The VB.NET version uses the AscW method rather than the more common CInt method to cast the value, since Char values in VB.NET cannot be converted to Integer. The AscW method returns an integer value representing the character code of a character.
The final three lines of code in Example 4-14 and Example 4-15 take the character entered, convert it to both upper- and lowercase, and fill in the appropriate labels.
Suppose you want to intercept the keystroke and selectively replace it with a different character. For example, suppose you want to intercept all dollar signs ($) and replace them with a number sign (#). You could do this by handling the Validating event (demonstrated in the next section), but often a better way would be to change the character before it is even displayed on the screen. To do this, modify the KeyPress event-handler method to add the highlighted code shown in Example 4-16 (in C#) and Example 4-17 (in VB.NET).
Example 4-16. Character substitution in KeyPress event in C#
private void txtInput_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { char keyChar; keyChar = e.KeyChar; txtMsg.AppendText("KeyPress event." + "\r\n"); txtMsg.AppendText("\t" + "KeyChar: " + keyChar + "\r\n"); txtMsg.AppendText("\t" + "KeyChar Code: " + (int)keyChar + "\r\n"); txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n"); txtMsg.AppendText("\r\n"); // Fill in the Upper and Lower labels lblUpper.Text = keyChar.ToString( ).ToUpper( ); lblLower.Text = keyChar.ToString( ).ToLower( ); // Change $ to # if (keyChar.ToString( ) = = "$") { txtInput.AppendText("#"); e.Handled = true; } }
Example 4-17. Character substitution in KeyPress event in VB.NET
Private Sub txtInput_KeyPress(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) _ Handles txtInput.KeyPress Dim keyChar As Char keyChar = e.KeyChar txtMsg.AppendText("KeyPress event." + vbCrLf) txtMsg.AppendText(vbTab + "KeyChar: " + keyChar + vbCrLf) txtMsg.AppendText(vbTab + "KeyChar Code: " + _ AscW(keyChar).ToString( ) + vbCrLf) txtMsg.AppendText(vbTab + "Handled: " + e.Handled.ToString( ) + vbCrLf) txtMsg.AppendText(vbCrLf) ' Fill in the Upper and Lower labels Dim str As String = e.KeyChar.ToString( ) lblUpper.Text = keyChar.ToString( ).ToUpper( ) lblLower.Text = keyChar.ToString( ).ToLower( ) ' Change $ to # If (keyChar.ToString( ) = "$") Then txtInput.AppendText("#") e.Handled = True End If End Sub
When this code is run and a dollar sign (a shifted 4 on a U.S.
English keyboard) is entered in the input field, the events displayed
are KeyDown for Shift, KeyDown for 4, and KeyPress for $, just as
before. The Upper and Lower labels both display $, since that
character is unaffected by converting case. Before the event
finishes, though, the character is tested to see if it is a $. If so,
the AppendText instance method appends the desired character, the #
sign, to the text box. Then e.Handled is set to
true
. This suppresses all further handling of the
original keypress.
Several events can play a role in validating the contents of a TextBox, including the key events seen in the previous example. The sequence of events that occurs when a TextBox gains and loses focus are:
Enter
GotFocus
Leave
Validating
Validated
LostFocus
Of these, the GotFocus and LostFocus events are low-level events that are not typically used for validation. The Enter event is not useful for validation because it occurs before any data entry can occur. The Leave event is also not usually used for validation because its event argument, EventArgs, does not expose any properties for influencing the event.
Table 4-10 summarizes the most useful events for validating a TextBox.
Table 4-10. TextBox events available for validation
Event name |
Event argument |
Description |
---|---|---|
KeyPress |
KeyPressEventArgs |
Use the KeyPressEventArgs.Handled property to suppress keystrokes. |
TextChanged |
EventArgs |
Raised if the Text property changed, either by user interaction or under programmatic control. Fires with every character entered in a TextBox. |
Validating |
CancelEventArgs |
Raised after focus leaves the control and enters a control that has
CausesValidation set to |
Validated |
EventArgs |
Raised after control is finished validating (after the Validating event). |
In the following example, you will see the KeyPress and Validating events used to control and validate data entered in a TextBox. The example will allow a user to enter an ISBN number, which will then be validated.
International Standard Book Number (ISBN) numbers are used by the book industry to track and uniquely identify book titles. They are typically found on the back cover of books, often in conjunction with a bar code. There is more to ISBN numbers than the information discussed here (for example, the meaning of the different portions of the number and how they are assigned). All you need to know for this example, however, is that an ISBN number consists of nine digits, called the true number, plus one check digit or the letter X (for check-digit value 10). The digits may be separated into sections separated by hyphens. The hyphens must be allowed but are ignored.
The algorithm for calculating the check digit is as follows: Multiply the first digit in the true number by 10, the next digit by 9, the next by 8, and so on until the last digit is multiplied by 2. Add all these products together. The number needed to increase that sum to the next multiple of 11 is the check digit (that is, the check digit is the sum of the products modulo 11). If the check "digit" turns out to be 10, use the letter X instead.
To demonstrate how this works, open Visual Studio .NET and create a new Windows application project called IsbnValidate. Add the controls and set the properties listed in Table 4-11.
Table 4-11. IsbnValidate controls
Control |
Name |
Property |
Value |
---|---|---|---|
Form |
Form1 |
Size |
272,320 |
Text |
ISBN Validation | ||
Label |
label1 |
Location |
48,16 |
Font |
Tahoma, 14.25pt, Bold Italic | ||
Size |
176,23 | ||
Text |
ISBN Validation | ||
TextBox |
txtInput |
Location |
72,64 |
Size |
100,20 | ||
Text |
<blank> | ||
Label |
label2 |
Location |
24,104 |
Size |
80,23 | ||
Text |
True Number: | ||
Label |
label3 |
Location |
32,152 |
Size |
72,23 | ||
Text |
Check Digit: | ||
Label |
lblTrue |
BorderStyle |
Fixed3D |
Location |
112,104 | ||
Size |
100,23 | ||
Text |
<blank> | ||
Label |
lblCheck |
BorderStyle |
Fixed3D |
Location |
112,152 | ||
Size |
100,23 | ||
Text |
<blank> | ||
Label |
lblResults |
Location |
56,192 |
Size |
152,24 | ||
Text |
<blank> | ||
Button |
btnClear |
Location |
88,240 |
Size |
75,23 | ||
Text |
Clear |
When all the controls are in place, the form layout should look similar to Figure 4-11.
Most validation work will occur in the Validating event of the
input TextBox, which takes CancelEventArgs as its event argument.
CancelEventArgs has one property: Cancel. When set to
true
, all events that would normally occur after
the Validating event are suppressed. This means that the Validated
and LostFocus events do not fire, and the cursor cannot leave the
control.
Implement the Validating event handler in C# by highlighting the input TextBox control, clicking on the Events icon (
) in the Properties window, scrolling to the Validating event, and entering the event handler method name: IsbnValidate. In VB.NET, go to the code-editing window, select the txtInput control from the drop-down list at the top left of the window, then scroll to the Validating event in the right drop-down. The method skeleton will have the default name of txtInput_Validation. Change it to IsbnValidate.
Enter the highlighted code from Example 4-18 into the C# IsbnValidation code skeleton or the highlighted code from Example 4-19 for into the VB.NET code skeleton.
Example 4-18. IsbnValidation event handler in C#
private void IsbnValidate(object sender, System.ComponentModel.CancelEventArgs e) { string strTrue; string strCheck; string strIsbn = ""; string strPad; int sum = 0; int pad; // Get the string from the TextBox TextBox tb = (TextBox)sender; string strInput = tb.Text; try { for (int i = 0; i < strInput.Length; ++i) { if ( (strInput[i] >= '0' && strInput[i] <= '9') || strInput[i] = = 'x' || strInput[i] = = 'X' ) strIsbn += strInput[i]; } if (strIsbn.Length != 10) throw new Exception( ); // extract true number strTrue = strIsbn.Substring(0,9); // extract check digit strCheck = strIsbn.Substring(9,1); lblTrue.Text = strTrue; lblCheck.Text = strCheck; // Calculate the check digit from the true ISBN number. // First do the multiplying and add up the products. for (int i = 0; i < strTrue.Length; ++i) { String testString = strTrue.Substring(i,1); int testInt = Convert.ToInt32(testString); sum += testInt * (10 - i); } // Calculate the number needed to pad to a multiple of 11 pad = 11 - (sum % 11); // assign digit or X strPad = pad = = 10 ? "X" : pad.ToString( ); // Compare the pad w/ strCheck. if (strCheck != strPad) throw new Exception( ); lblResults.ForeColor = Color.Green; lblResults.Text = "Valid ISBN Number."; } catch { e.Cancel = true; tb.Select(0,tb.Text.Length); lblResults.ForeColor = Color.Red; lblResults.Text = "Invalid ISBN Number."; } }
Example 4-19. IsbnValidation event handler in VB.NET
Private Sub IsbnValidation(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles txtInput.Validating Dim strTrue As String Dim strCheck As String Dim strIsbn As String = "" Dim strPad As String Dim Sum As Integer = 0 Dim Pad As Integer Dim i As Integer ' Get the string from the TextBox Dim tb As TextBox = CType(sender, TextBox) Dim strInput As String = tb.Text Try For i = 0 To strInput.Length - 1 If ((strInput.Chars(i) >= "0"c And _ strInput.Chars(i) <= "9"c) Or _ strInput.Chars(i) = "x"c Or _ strInput.Chars(i) = "X"c) Then strIsbn += strInput.Chars(i) End If Next i If strIsbn.Length <> 10 Then Throw New Exception( ) End If strTrue = strIsbn.Substring(0, 9) strCheck = strIsbn.Substring(9, 1) lblTrue.Text = strTrue lblCheck.Text = strCheck ' Calculate the check digit from the true ISBN number. ' First do the multiplying and add up the products. For i = 0 To strTrue.Length - 1 Dim testString As String = strTrue.Substring(i, 1) Dim testInt As Integer = Convert.ToInt32(testString) Sum += testInt * (10 - i) Next i ' Calculate the number needed to pad to a multiple of 11 Pad = 11 - (Sum Mod 11) ' assign digit or X strPad = CStr(iif(Pad <> 10, Pad.ToString( ), "X")) ' Compare the pad w/ strCheck. If strCheck <> strPad Then Throw New Exception( ) End If lblResults.ForeColor = Color.Green lblResults.Text = "Valid ISBN Number." Catch e.Cancel = True tb.Select(0, tb.Text.Length) lblResults.ForeColor = Color.Red lblResults.Text = "Invalid ISBN Number." End Try End Sub
The first several lines
in
the body of the IsbnValidate method simply declare several member
variables for later use. Notice that two of the variables, strIsbn
and Sum, are also instantiated at this point. Both variables are used
with the +=
operator; an error will occur if the
variable is not instantiated prior to the first use.
The next two lines get the value of the Text property of txtInput:
TextBox tb = (TextBox)sender; string strInput = tb.Text;
Dim tb As TextBox = CType(sender, TextBox) Dim strInput As String = tb.Text
The sender argument is of type object and so must be cast to type TextBox before the Text property can be retrieved. It would have been equally valid in this example to replace these lines with:
string strInput = txtInput.Text;
Dim strInput As String = txtInput.Text
ibut the former syntax is more robust, since it is not tied to a specific control.
A try . . . catch block is used for the actual validation. The code in the try block is executed. If any errors occur, or if an exception is thrown intentionally, then program execution moves immediately to the catch block and the balance of the code in the try block is never executed. This construct allows a series of tests and an easy and logical way to handle any errors that may arise.
Tip
For more on exception handling, see Chapter 21.
The try block first iterates through the input string, filtering out any characters without a valid ISBN number. Remember that ISBN numbers may be printed on books (and entered in this program) with hyphens, which must be removed. The only valid characters are digits or the letter X. In C#, this filtering is accomplished with this code snippet:
for (int i = 0; i < strInput.Length; ++i) { if ( (strInput[i] >= '0' && strInput[i] <= '9') || strInput[i] = = 'x' || strInput[i] = = 'X' ) strIsbn += strInput[i]; }
Tip
In C#, the bracket following the string variable name is a zero-based
indexer into the string. For example, strInput[2]
would refer to the third character in the string. The return type of
a string indexer is of type char, i.e., a Unicode character. The
characters on the right side of the equality operators (=
=
) are not strings, but chars, as indicated by the single
quotes.
In VB.NET, the filtering is accomplished with this code snippet:
For i = 0 To strInput.Length - 1 If ((strInput.Chars(i) >= "0"c And _ strInput.Chars(i) <= "9"c) Or _ strInput.Chars(i) = "x"c Or _ strInput.Chars(i) = "X"c) Then strIsbn += strInput.Chars(i) End If Next i
In VB.NET, there is no string indexer, per se, so the String.Chars
property is used. This returns a char, so the characters on the right
side of the equality operators (=
) are chars, as
indicated by the trailing cs.
Note that this filtering algorithm allows the X character to be in any position, whereas it is valid only if it is in the final position. You could modify the code to test for the position, but invalid positions will be caught further along in the program.
The next few lines test the length of the string, since a full ISBN number is, by definition, exactly 10 digits long (9 + 1). If the string is not the correct length, an exception is thrown, which stops program execution in the try block and moves it to the catch block:
if (strIsbn.Length != 10) throw new Exception( );
If strIsbn.Length <> 10 Then Throw New Exception( ) End If
The next several lines extract substrings from the full ISBN number (strIsbn) to get the true ISBN number (strTrue) and the check digit (strCheck). These values are then displayed in the lblTrue and lblCheck labels.
Now comes the meat of the matter. A check digit is calculated from the true ISBN number, and then compared to the check digit extracted from the full ISBN number. If the digits are the same, the number is valid, and an appropriate message is displayed in lblResults. If not, then the ISBN number is invalid and an exception is thrown.
First the sum of the products is calculated. In C#, this is done with the following lines of code. Note the use of the Substring method, with the resulting string's conversion to an integer before multiplication and the addition of the product to the integer Sum. You cannot use the string indexer here unless you include a ToString method call because it results in a char. If you convert the char object to an integer, you get the Unicode key code (effectively the character's ASCII value) rather than its numeric value.
for (int i = 0; i < strTrue.Length; ++i) { String testString = strTrue.Substring(i,1); int testInt = Convert.ToInt32(testString); sum += testInt * (10 - i); }
In VB.NET, the sum of the products is calculated with these lines of code:
For i = 0 To strTrue.Length - 1 Dim testString As String = strTrue.Substring(i, 1) Dim testInt As Integer = Convert.ToInt32(testString) sum += testInt * (10 - i) Next i
The check digit is computed by getting the remainder of the Sum divided by 11 using the modulus operator, and then subtracting that remainder from 11. If the remainder is 10, then the check digit is set to X. In C#, this last test is accomplished with the ternary operator (?):
pad = 11 - (sum % 11); strPad = pad = = 10 ? "X" : pad.ToString( );
and in VB.NET, the test is done with the if statement:
Pad = 11 - (Sum Mod 11) strPad = CStr(iif(Pad <> 10, Pad.ToString( ), "X"))
Finally the calculated check digit is compared to the original check digit. If they are not equal, an exception is thrown. Otherwise, a message, in green, is displayed in lblResults.
The catch block first sets the CancelEventArgs.Cancel property to
true
. This has the effect of suppressing all
events further in this event stream, preventing the focus from
leaving the field. (As you will see, this causes problems that will
be dealt with shortly.) The text in the field is highlighted with the
TextBox Select method and a message, in red, is displayed in
lblResults. The catch block looks like:
catch { e.Cancel = true; tb.Select(0,tb.Text.Length); lblResults.ForeColor = Color.Red; lblResults.Text = "Invalid ISBN Number."; }
Catch e.Cancel = True tb.Select(0, tb.Text.Length) lblResults.ForeColor = Color.Red lblResults.Text = "Invalid ISBN Number." End Try
Before running the program, implement the Click event for the Clear button. The Clear button will clear the edit field and the three labels that the application fills in. Double-click on the button in design mode to open up a code skeleton for Click, the default event for a button. Then enter the highlighted code in Example 4-20 for the C# version and the highlighted code in Example 4-21 for the VB.NET version.
Run the application. After entering a valid ISBN number (from the back of any book) and tabbing out of the TextBox, the window will look like Figure 4-12.
Clicking the Clear button will clear all the fields. If you entered
an invalid ISBN number, the message will display Invalid
ISBN Number
, in red. However, entering an invalid number
creates a problem: you can't leave the TextBox. You
also can't click the Clear button, or even close the
window. This is because the catch block sets the
CancelEventArgs.Cancel property to true
whenever
it encounters an invalid number.
Tip
If you ran the program from within Visual Studio .NET, you can kill the program with the Debug → Stop Debugging menu item, or by pressing Shift+F5. If you built an EXE file and ran it outside Visual Studio .NET, you have to use the Windows Task Manager to kill the application.
One solution to this problem is to add a few lines of code to the beginning of the IsbnValidate method to test for an empty TextBox:
if (strInput.Length = = 0) return;
If strInput.Length = 0 Then Return End If
This tests the number of characters in the txtInput. If the TextBox is empty, it returns without any further processing. CancelEventArgs.Cancel never gets set, so focus can leave the control.
This validation example is now totally workable. You can enter any characters in the TextBox, and it will be validated. However, you can make one more refinement. Suppose you don't want invalid characters to even display in the input TextBox. The only possible valid characters are the digits, the hyphen, and the upper- and lowercase X. You can suppress every character except for these valid characters from displaying in the control by handling the KeyPress event.
Create a code skeleton for the KeyPress event for the txtInput control with the techniques described above. Then enter the highlighted code in Example 4-22 for the C# version or the highlighted code in Example 4-23 for the VB.NET version.
Example 4-22. txtInput KeyPress event handler in C#
private void txtInput_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e) { char keyChar; keyChar = e.KeyChar; // Suppress any keys except digits,X,x,hyphen,Backspace,or Enter if(!Char.IsDigit(keyChar) // 0 - 9 && keyChar != 8 // backspace && keyChar != 13 // enter && keyChar != 'X' && keyChar != 'x' && keyChar != 45 // hyphen ) { // Do not display the keystroke e.Handled = true; }
Example 4-23. txtInput KeyPress event handler in VB.NET
Private Sub txtInput_KeyPress(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyPressEventArgs) _ Handles txtInput.KeyPress Dim keyChar As Char keyChar = e.KeyChar ' Suppress any keys except digits,X,x,hyphen (45),Backspace (8), ' or Enter (13) If ((Not Char.IsDigit(keyChar)) _ And (AscW(keyChar) <> 8) _ And (AscW(keyChar) <> 13) _ And (keyChar <> "X"c) _ And (keyChar <> "x"c) _ And (AscW(keyChar) <> 45)) Then ' Do not display the keystroke e.Handled = True End If End Sub
Both versions of the KeyPress event handler assign the key to a
variable of type char. Then an if statement tests to see if the
character is allowable. If not, then e.Handled is set to
true
, which suppresses the character from being
displayed.
There are some differences in the syntax between C# and VB.NET. In
C#, the variable of type char can be compared directly against either
the key code value or the string representing the char, indicated by
the single quotes around the letters X and x. In VB.NET, by contrast,
the key code values can be compared only by using the AscW function,
which takes a char as the argument and returns an integer. Also, the
syntax in VB.NET for indicating a string
is actually a char
is that appends the letter c to the string, as in
"X"c
.
Get Programming .NET Windows Applications now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.