Some Examples

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.

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.

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. 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.

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. 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.

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>

KeyEvents form layout

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#

image with no caption

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

image with no caption

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:

image with no caption

private string strMsg = "";

image with no caption

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 (

image with no caption

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

image with no caption

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

image with no caption

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.

KeyEvents application showing a shifted G

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:

image with no caption

txtMsg.AppendText("\t" + "KeyCode name: " + e.KeyCode + "\r\n");
txtMsg.AppendText("\t" + "KeyCode key code: " + ((int)e.KeyCode) + 
                 "\r\n");

image with no caption

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:

image with no caption

txtMsg.AppendText("\t" + "KeyData name: " + e.KeyData + "\r\n");
txtMsg.AppendText("\t" + "KeyData key code: " + ((int)e.KeyData) + 
                  "\r\n");

image with no caption

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:

image with no caption

txtMsg.AppendText("\t" + "KeyValue: " + e.KeyValue + "\r\n");

image with no caption

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:

image with no caption

txtMsg.AppendText("\t" + "Handled: " + e.Handled + "\r\n");

image with no caption

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#

image with no caption

                  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

image with no caption

                  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:

image with no caption

KeyMsgBox("KeyDown", e);

image with no caption

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#

image with no caption

private void txtInput_KeyUp(object sender, 
                            System.Windows.Forms.KeyEventArgs e)
{
   KeyMsgBox("KeyUp", e);
}

Example 4-13. KeyUp event in VB.NET

image with no caption

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.

KeyDown and KeyUp events for shifted G

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

image with no caption

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

image with no caption

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.

KeyPress event handler output

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:

image with no caption

txtMsg.AppendText("\t" + "KeyChar: " + keyChar + "\r\n");
txtMsg.AppendText("\t" + "KeyChar Code: " + (int)keyChar + "\r\n");

image with no caption

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:

image with no caption

char myChar = 'A';

In VB.NET, you append the letter c, as in:

image with no caption

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#

image with no caption

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

image with no caption

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.

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:

  1. Enter

  2. GotFocus

  3. Leave

  4. Validating

  5. Validated

  6. 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 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.

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.

ISBN validator design layout

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 (

image with no caption

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

image with no caption

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

image with no caption

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:

image with no caption

TextBox tb = (TextBox)sender;
string strInput = tb.Text;

image with no caption

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:

image with no caption

string strInput = txtInput.Text;

image with no caption

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:

image with no caption

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:

image with no caption

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:

image with no caption

if (strIsbn.Length != 10)
   throw new Exception(  );

image with no caption

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.

image with no caption

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:

image with no caption

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 (?):

image with no caption

pad = 11 - (sum % 11);
strPad = pad =  = 10 ? "X" : pad.ToString(  );

and in VB.NET, the test is done with the if statement:

image with no caption

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:

image with no caption

catch
{
   e.Cancel = true;
   tb.Select(0,tb.Text.Length);
   lblResults.ForeColor = Color.Red;
   lblResults.Text = "Invalid ISBN Number.";
}

image with no caption

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#

image with no caption

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

image with no caption

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.

IsbnValidate showing a valid ISBN number

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.

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:

image with no caption

if (strInput.Length =  = 0)
   return;

image with no caption

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#

image with no caption

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

image with no caption

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.