Except for the key-state toggling properties (CapsLock, NumLock, and ScrollLock), creating the Keyboard class was simple. Each other property corresponds either to a single function call or to the conglomerate SystemParametersInfo function. For example, to retrieve the keyboard type, the class calls the GetKeyboardType API function. To modify the caret blink rate, the code calls the GetCaretBlinkTime and SetCaretBlinkTime API functions.
The CapsLock, NumLock, and ScrollLock properties each use the same Windows API function, GetKeyState, to check the state of the specific key. To use the function, you must pass it a specific virtual key code. Windows assigns every physical key a mapping so that Windows can run on machines with keyboards that differ slightly from the standard PC keyboard, and these mappings are called virtual key codes. Windows supports a maximum of 256 such mappings. Windows uses the value 20 for the CapsLock key, 144 for the NumLock key, and 145 for the ScrollLock key. VBA provides constants representing these (and most other) virtual key codes: vbKeyCapital for the CapsLock key, vbKeyNumLock for the NumLock key, and vbKeyScrollLock for the ScrollLock key. (Actually, not all VBA hosts define the vbKeyScrollLock constant, so we’ve included a declaration for this value in KEYBOARD.CLS.) Therefore, the following fragment will return the current state of the CapsLock key:
lngState = GetKeyState(vbKeyCapital)
GetKeyState returns an integer containing 1 in its lowest bit if the key is toggled on or 0 in the lowest bit if the key is toggled off. (GetKeyState returns the highest bit set if the key is currently being held down, but the key being held down adds no information as to whether the key is currently toggled on or off.) The property procedures then convert these values to True or False, looking at just the lowest bit by using the logical And operator:
Property Get Capslock() As Boolean
' Return or set the Capslock toggle.
Capslock = CBool(GetKeyState(vbKeyCapital) And 1)
End Property
The CapsLock, NumLock, and ScrollLock Property Let procedures each call the same subroutine, SetKeyState, to do their work. SetKeyState uses the API functions GetKeyboardState and SetKeyboardState; each of these functions uses a 256-byte buffer in which to retrieve or set the full set of 256 key settings. Each virtual key code maps to one element in this array of values. Once you’ve used GetKeyboardState to retrieve the bufferful of settings, you can modify the specific key setting you care about and then store the settings back using SetKeyboardState. This will change just the keys you’ve modified. This method allows you to change the state of the CapsLock, NumLock, or ScrollLock key. Listing 9.9 shows the SetKeyState procedure.
Listing 9.9: The SetKeyState Procedure Allows You to Alter the State of a Particular Key
Private Sub SetKeyState(intKey As Integer, fTurnOn As Boolean)
' Retrieve the keyboard state, set the particular
' key in which you’re interested, and then set
' the entire keyboard state back the way it
' was, with the one key altered.
Dim abytBuffer(0 To 255) As Byte
GetKeyboardState abytBuffer(0)
abytBuffer(intKey) = CByte(Abs(fTurnOn))
SetKeyboardState abytBuffer(0)
End Sub
The only parameter the GetKeyboardState and SetKeyboardState functions expect to receive is the address of a 256-byte memory block. In VBA, you emulate this block using a 256-element byte array. To pass an array to an API call, you normally pass the first element of the array. Because VBA stores arrays contiguously in memory, the API function can take the address of the first element of the array and find the rest of the elements. Procedures such as SetKeyState depend on this behavior when passing an array to an API call that doesn’t understand anything about VBA arrays.