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.
4.3.1 Keyboard Events
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.
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.)
Property |
Data type |
Description |
---|---|---|
Alt |
Boolean |
Read-only value indicating if the Alt key was pressed. true if pressed, false otherwise. |
Control |
Boolean |
Read-only value indicating if the Ctrl key was pressed. true if pressed, false otherwise. |
Shift |
Boolean |
Read-only value indicating if the Shift key was pressed. true if pressed, false otherwise. |
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. false until set otherwise. |
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. |
|
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.
Property |
Description |
---|---|
Handled |
Boolean value indicating if the event was handled. false until set otherwise. When true, the keystroke is not displayed. |
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.
Control |
Name |
Property |
Value |
---|---|---|---|
Form |
Form1 |
Size |
425,320 |
Text |
Key Event Demonstrator |
||
TextBox |
txtInput |
Location |
8,8 |
Multiline |
False |
||
Size |
100,20 |
||
Text |
|||
TextBox |
txtMsg |
Location |
8,40 |
MultiLine |
True |
||
ScrollBars |
Vertical |
||
Size |
304,232 |
||
TabStop |
False |
||
Text |
|||
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 |
|||
Label |
lblLower |
BorderStyle |
Fixed3D |
Location |
368,104 |
||
Size |
32,23 |
||
Text |
Figure 4-7. KeyEvents form layout
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.
Example 4-6. btnReset Click event handler in C#
private void btnReset_Click(object sender, System.EventArgs e) { strMsg = ""; txtMsg.Text = strMsg; txtInput.Text = ""; lblUpper.Text = ""; lblLower.Text = ""; }
Example 4-7. btnReset Click event handler in VB.NET
Private Sub btnReset_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnReset.Click strMsg = "" txtMsg.Text = strMsg txtInput.Text = "" lblUpper.Text = "" lblLower.Text = "" End Sub
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." + " "); txtMsg.AppendText(" " + "KeyCode name: " + e.KeyCode + " "); txtMsg.AppendText(" " + "KeyCode key code: " + ((int)e.KeyCode) + " "); txtMsg.AppendText(" " + "KeyData name: " + e.KeyData + " "); txtMsg.AppendText(" " + "KeyData key code: " + ((int)e.KeyData) + " "); txtMsg.AppendText(" " + "KeyValue: " + e.KeyValue + " "); txtMsg.AppendText(" " + "Handled: " + e.Handled + " "); txtMsg.AppendText(" "); }
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. KeyEvents application showing a shifted G
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(" " + "KeyCode name: " + e.KeyCode + " "); txtMsg.AppendText(" " + "KeyCode key code: " + ((int)e.KeyCode) + " ");
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.
VB.NET constant |
C# escape sequence |
KeyCode value (decimal) |
Meaning |
---|---|---|---|
vbCr |
|
13 |
Carriage return |
vbCrLf |
|
13 & 10 |
Carriage return/line-feed combination |
vbFormFeed |
f |
12 |
Form feed |
vbLf |
|
10 |
Line feed (new line) |
vbTab |
|
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(" " + "KeyData name: " + e.KeyData + " "); txtMsg.AppendText(" " + "KeyData key code: " + ((int)e.KeyData) + " ");
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(" " + "KeyValue: " + e.KeyValue + " ");
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(" " + "Handled: " + e.Handled + " ");
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." + " "); txtMsg.AppendText(" " + "KeyCode name: " + e.KeyCode + " "); txtMsg.AppendText(" " + "KeyCode key code: " + ((int)e.KeyCode) + " "); txtMsg.AppendText(" " + "KeyData name: " + e.KeyData + " "); txtMsg.AppendText(" " + "KeyData key code: " + ((int)e.KeyData) + " "); txtMsg.AppendText(" " + "KeyValue: " + e.KeyValue + " "); txtMsg.AppendText(" " + "Handled: " + e.Handled + " "); txtMsg.AppendText(" "); } 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.
Example 4-12. KeyUp event in C#
private void txtInput_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e) { KeyMsgBox("KeyUp", e); }
Example 4-13. KeyUp event in VB.NET
Private Sub txtInput_KeyUp(ByVal sender As Object, _ ByVal e As System.Windows.Forms.KeyEventArgs) _ Handles txtInput.KeyUp KeyMsgBox("KeyUp", e) End Sub
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.
Figure 4-9. KeyDown and KeyUp events for shifted G
The KeyDown and KeyUp events provide a lot of information, but often you really care about the ASCII value of the keystrokei.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." + " "); txtMsg.AppendText(" " + "KeyChar: " + keyChar + " "); txtMsg.AppendText(" " + "KeyChar Code: " + (int)keyChar + " "); txtMsg.AppendText(" " + "Handled: " + e.Handled + " "); txtMsg.AppendText(" "); // 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.
Figure 4-10. KeyPress event handler output
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(" " + "KeyChar: " + keyChar + " "); txtMsg.AppendText(" " + "KeyChar Code: " + (int)keyChar + " ");
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.
|
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." + " "); txtMsg.AppendText(" " + "KeyChar: " + keyChar + " "); txtMsg.AppendText(" " + "KeyChar Code: " + (int)keyChar + " "); txtMsg.AppendText(" " + "Handled: " + e.Handled + " "); txtMsg.AppendText(" "); // 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.
4.3.2 TextBox Validation
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:
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.
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 true. If the CancelEventArgs.Cancel property is set to true, then the current event is canceled, the Validated event is suppressed, and the focus is forced to remain in the control. |
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.
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 |
|||
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 |
|||
Label |
lblCheck |
BorderStyle |
Fixed3D |
Location |
112,152 |
||
Size |
100,23 |
||
Text |
|||
Label |
lblResults |
Location |
56,192 |
Size |
152,24 |
||
Text |
|||
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.
Figure 4-11. ISBN validator design layout
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.
|
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]; }
|
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.
Example 4-20. IsbnValidate Clear button Click event handler in C#
private void btnClear_Click(object sender, System.EventArgs e) { txtInput.Text = ""; lblResults.Text = ""; lblTrue.Text = ""; lblCheck.Text = ""; }
Example 4-21. IsbnValidation Clear button Click event handler in VB.NET
Private Sub btnClear_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnClear.Click txtInput.Text = "" lblResults.Text = "" lblTrue.Text = "" lblCheck.Text = "" End Sub
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.
Figure 4-12. IsbnValidate showing a valid ISBN number
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.
|
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.
Windows Forms and the .NET Framework
Getting Started
Visual Studio .NET
Events
Windows Forms
Dialog Boxes
Controls: The Base Class
Mouse Interaction
Text and Fonts
Drawing and GDI+
Labels and Buttons
Text Controls
Other Basic Controls
TreeView and ListView
List Controls
Date and Time Controls
Custom Controls
Menus and Bars
ADO.NET
Updating ADO.NET
Exceptions and Debugging
Configuration and Deployment