Recipe 11.3 Classify Keypresses in a Language-Independent Manner

11.3.1 Problem

You need to be able to classify a keypress as a character, a digit, or neither. You also need to know if a character is uppercase or lowercase. You know you can write code to handle this, but if you do that, you're limiting yourself to a single national language, since languages classify their characters differently. Since Windows knows about various character sets, is there some way you can use Windows to do this work for you?

11.3.2 Solution

You could write VBA code to classify characters, but it wouldn't be language-independent. For example, the ANSI character 65 is an uppercase character in the standard multinational character set, but it may be different in another character set. If you want your applications to work in various languages, you must not assume specific character ranges. The Windows API includes a number of functions you can call to categorize characters based on their ANSI values. The isCharAlpha and isCharAlphaNumeric functions both are faster than the built-in VBA functions and are able to deal with international issues. Luckily, an ANSI value is exactly what the KeyPress event procedure in Access sends you, so you can use these functions from within KeyPress event procedures that you write.

In addition to the necessary function declarations, the sample database 11-03.MDB includes a demonstration form showing all the ANSI characters and their classifications. Load and run frmCharClasses from 11-03.MDB, and you'll see a display like that in Figure 11-3. By scrolling through the form, you'll be able to see all 255 ANSI characters and their classifications.

Figure 11-3. frmCharClasses shows all the ANSI characters and their classifications
figs/acb2_1103.gif

To use this functionality in your own applications, follow these steps:

  1. Import the module basClassifyChars from 11-03.MDB into your application.

  2. To classify an ANSI value, call one or more of the functions in Table 11-2. Each of these functions takes as its parameter a value between 1 and 255. Each function returns a nonzero value if the character code you passed is a member of the function's tested group, or 0 if it's not. (As you can see from Table 11-2, some of the functions come directly from the Windows API and others return values based on those functions.) These functions will return correct values no matter which language version of Windows is running.

Table 11-2. The character classification functions in basClassifyChars

Function

API?

Inclusion class

acb_apiIsCharAlphaNumeric

Yes

Language-defined alphabetic or numeric characters

acb_apiIsCharAlpha

Yes

Language-defined alphabetic characters

acbIsCharNumeric

No

Alphanumeric, but not alphabetic

acbIsSymbol

No

Not alphanumeric

acb_apiIsCharUpper

Yes

Language-defined uppercase characters

acb_apiIsCharLower

Yes

Language-defined lowercase characters

For example, imagine that you need to limit the number of characters typed into a text box, and the number of allowable characters isn't known until runtime. In addition, you want to allow only alphabetic or numeric values, but that isn't known until runtime either. Although you could programmatically control the input masks, creating a new one each time conditions change, it is simpler to handle this problem using the KeyPress event and some code that checks the state of the current keypress. The sample form, frmInputTest (Figure 11-4), shows a simple test form. The text box labeled "Enter some characters" allows you to enter up to as many characters as shown in the "Maximum number of characters" text box, and you can enter only characters whose type you've chosen in the character type option group.

Figure 11-4. frmInputTest uses character classifications to disallow keypresses
figs/acb2_1104.gif

The code attached to txtCharTest's KeyPress event looks like this:

Sub txtCharTest_KeyPress (KeyAscii As Integer)
   
   ' Always allow a backspace.
   If KeyAscii = vbKeyBack Then Exit Sub

   ' If txtChars is non-null and greater than 0, and txtCharTest
   ' is non-null and has too many characters, set KeyAscii to 0.
   If Not IsNull(Me.txtChars) Then
      If Me.txtChars > 0 Then
         If Not IsNull(Me.txtCharTest.Text) Then
            If Len(Me.txtCharTest.Text) >= Me.txtChars Then
               KeyAscii = 0
            End If
         End If
      End If
   End If
   ' In any case, if the keypress isn't the correct type,
   ' set KeyAscii to 0.
   If Me.grpCharType = 1 Then
      If (acb_apiIsCharAlpha(KeyAscii) = 0) Then KeyAscii = 0
   Else
      If (acbIsCharNumeric(KeyAscii) = 0) Then KeyAscii = 0
   End If
End Sub

In the KeyPress event, Access sends you the parameter KeyAscii, which contains the ANSI value of the key that was just pressed. To tell Access to disregard this key, modify its value to 0 during the event procedure. In this case, if there's no room left in the field (based on the number in Me.txtChars) or if the character is not the right type (based on calls to acb_apiIsCharAlpha and acbIsCharNumeric), the code sets the value of KeyAscii to 0, causing Access to disregard the keypress. Play with the sample form, changing the values, to see how the code works.

11.3.3 Discussion

Windows internally maintains information about the currently selected language and character set. For each language, certain characters are treated as uppercase and others aren't. Some characters in the character set represent alphabetic characters and others don't. It would be impractical to maintain this information for each language your application might use. Luckily, you don't have to manage this. The Access UCase and LCase functions handle case conversions for you, but Access doesn't include case-testing functions. That's the role of the functions introduced in this solution: they allow you to test the classification of characters, no matter what the language. Attempting to perform this task in VBA will cause you trouble if you plan on working internationally.

You may not need these routines often, but when you do, the API versions are both faster and more reliable than handwritten code would be. Don't count on specific ANSI values to be certain characters, uppercase or lowercase, because these values change from version to version of internationalized Windows.