This short but important chapter will introduce you to handling user input via the keyboard. Key concepts covered include what events are triggered when a user presses a key and how to limit the user's input. Also covered is a Paradox trick for interrupting a loop and sendKeys().
The Path of a Keystroke
A keycode is a code that represents a keyboard character in ObjectPAL. A keycode can be an ASCII number, an IBM extended keycode number, or a string that represents a keyname known to Paradox. When a user presses a key on the keyboard, one of two things occurs. Either Windows processes it, or Paradox processes it. Windows processes it, for example, when the sequence ctrl-esc is used. When Paradox processes it, the keyPhysical event always sees it, and in the case of a character, the keyPhysical event passes the event to the keyChar event (see Figure 16-1).
Figure 1: The path of a key event
The steps to trapping a keystroke are as follows:
Decide whether you need to trap for a character or virtual key. If you want to trap for a character such as a, A, b, B, 1, !, or @, then use either the keyPhysical event or the keyChar event. If you want to trap for virtual keys such as F1 or esc, then you must use the keyPhysical event.
Decide at which level you need to trap for the key press. The two usual places are the form’s prefilter or directly on the field.
Inspect the eventInfo packet with either char() or vChar() to trap for the keystroke. If case does not matter, then use vChar(). If case does matter, then use char().
Note: As long as a field has focus, keystrokes do not bubble because the field uses up the keystroke. Therefore, the two best choices to trap for keystrokes are the form’s prefilter or on the field itself.
Using KeyEvents Methods and Procedures
The eventInfo passed to both the keyChar and keyPhysical events is of type KeyEvent. This means you can use the KeyEvent methods and procedures to enable you to get and set information about keystroke events. The keyEvent object type inherits some methods and procedures from the event object type. For example, the following traps for ctrl-shift-F1 using several keyEvent methods.
; Field :: keyPhysical method keyPhysical(var eventInfo KeyEvent) ;Start by disabling all keys, then we'll ;either do our processing or enable the ;keystroke at the end of this method. DisableDefault
switch case eventInfo.isControlKeyDown() : switch case eventInfo.isShiftKeyDown() : switch case eventInfo.vCharCode() = VK_F1 : msgInfo("", "You pressed cntrl + shft + F1") return otherwise : ;Another key was pressed. endSwitch otherwise : ; No shift keystroke. endSwitch otherwise : ; No control keystroke. endSwitch
enableDefault ;Allow all other keystrokes. endMethod
Refer to KeyEvent Type in the ObjectPAL Reference help file for a complete list of methods.
Interrupting a Loop
Sometimes you may want to give the user the option of interrupting a loop. For example, if you are scanning through a large table, you could allow the user to abort the procedure in midstream. This type of control adds a touch of professionalism to your application. The following example demonstrates how you can enable the user to interrupt a loop by pressing the esc key.
Suppose that you want to loop to 1,000 and display the counter in the status bar as the loop increments. The twist on this example is that you need to enable the user to press esc to interrupt the loop. This example uses the form’s prefilter with vChar() to trap a keystroke.
Create a new form and place a button labeled Count to 1000 and a text box on the form (see Figure 16-2).
Figure 2: Set up form
Add line 3 to the Var window of the form.
1: ;Form :: Var 2: Var 3: lFlag Logical 4: endVar
Add lines 5–7 to the keyPhysical event of the form.
1: ;Form :: keyPhysical 2: method keyPhysical(var eventInfo KeyEvent) 3: if eventInfo.isPreFilter() then 4: ;This code executes for each object on the form 5: if eventInfo.vchar() = "VK_ESCAPE" then 6: lFlag = True 7: endIf 8: else 9: ;This code executes only for the form 10: endIf 11: endMethod
Alter the mouseDown event of the text box as follows:
Add lines 3–16 to the pushButton event of the Count to 1000 button. Line 4 declares a variable private to a method for use in the for loop (lines 9–16). Line 7 sets the flag to False in case the user presses esc, setting the flag to True. Line 10 displays the value of siCounter in the status bar. Line 11 sleeps for the minimum amount of cycles (about 52 milliseconds), which is plenty of time to yield to Windows. This enables the esc key to sneak in. Line 12 checks whether the flag has changed to True. If the flag is True, line 13 displays a message, and line 14 quits the loop.
1: ;Button :: pushButton 2: method pushButton(var eventInfo Event) 3: var 4: siCounter SmallInt 5: endVar 6: 7: lFlag = False 8: 9: for siCounter from 1 to 1000 10: message(siCounter) 11: sleep() 12: if lFlag = True then 13: message("Counting interrupted") 14: quitloop 15: endIf 16: endFor 17: endMethod
Check the syntax, run the form, and click the button. As the computer counts to 1,000, the counter is shown in the status bar. Press esc or click the text box to interrupt the loop (see Figure 16-3).
Figure 3: Finished example of interrupting a loop
Note: Sometimes programmers use sleep(1) to indicate they want to release control to windows, but for the least amount of time. In place of sleep(1), you can use sleep() without a parameter--sleep(). In that case, Windows automatically permits about two events to occur.
Using keyPhysical
As already discussed, use the keyPhysical event when you want to trap for all keyboard keys. Use the keyChar event when you want to trap for only characters that are printable to the screen.
Suppose that you created two field objects: field1 and field2. You want field2 to echo whatever you type into field1—including backspace, delete, and enter—as though you were typing directly into field2. How do you do this?
A problem that often confronts users is that values aren’t committed to the field until endMethod. Remember that the default behavior occurs last in a method. Therefore, when you use the keyPhysical and keyChar events, invoke the default behavior to commit the last keystroke, as in the following example:
If you want to limit the user’s input, use either the keyChar or keyPhysical events of the input object. If you want to limit the user’s input to characters, use keyChar. If you want to control all keystrokes, use keyPhysical.
Suppose that you want to limit the user’s input to a list of specified keys. The technique presented here can be used to control any keys.
Create a form with a single undefined field (see Figure 16-4).
Figure 4: Setup form for limiting a user’s input example
Alter the keyPhysical event of the field as follows:
1: method keyPhysical(var eventInfo KeyEvent) 2: var 3: sChar String 4: endVar 5: 6: disableDefault 7: 8: sChar = eventInfo.char() 9: 10: switch 11: case eventInfo.vCharCode() = VK_DELETE : enableDefault 12: case eventInfo.vCharCode() = VK_BACK : enableDefault 13: case eventInfo.vCharCode() = VK_F8 : enableDefault 14: case sChar >="A" and sChar <="z" : enableDefault 15: case sChar >="0" and sChar <="9" : enableDefault 16: case sChar = "." : enableDefault 17: case sChar = "," : enableDefault 18: case sChar = " " : enableDefault 19: endSwitch 20: endMethod
Check the syntax, save the form as ONLYKEYS.FSL, and run it. Type some characters. Be sure to try numbers, letters, and special characters (see Figure 16-5).
Figure 5: ONLYKEYS.FSL Limits the user’s input to numbers, letters, and spaces
This is a good routine to turn into a custom method that you can call whenever you need to limit user input. Alter this routine in other ways to suit your needs—perhaps develop three or four custom methods for limiting user input. You can’t trap for everything, however. Some keys and key combinations are reserved by Windows, such as ctrl-F4.
You can’t store all characters in a Paradox table. This is a limitation of current computer technology. The issue is what Paradox should store to a table. For example, should it store Windows ANSI characters or OEM DOS characters? ANSI is an acronym for American National Standards Institute. The ANSI set consists of 8-bit codes that represent 256 standard characters, such as letters, numbers, and symbols. The ANSI set is used by Windows applications. extended ASCII is a character set designed by IBM. IBM extended the standard ASCII set from 7-bit codes to 8-bit codes and added more characters. The extended ASCII set contains 256 characters.
As a Windows product, the Paradox table structure has to be able to store Windows ANSI characters. Paradox supports dBASE and Paradox tables, however, and traditionally these table structures store OEM DOS characters. Therefore, Paradox must be able to deal with both character sets: the character set traditionally used by other products that used the table structures before Paradox and the character set used by Windows. In Paradox, the table language driver determines the character set. The problem is that although Microsoft controls both DOS and Windows, the two character sets, OEM and ANSI, are incompatible. You must decide between the two when you create your table.
One solution is to use the strict translation option of the Link tool. When strict translation is checked (the default), only the first 128 characters are stored. If you uncheck it, you enable your users to add characters that may not be supported by a different table language. There are disadvantages, however. For more information, refer to the online help on strict translation.
The wait argument indicates whether to send the keys immediately (True), or to wait until the current method has finished (False). In most cases, False is the preferred setting.
Now type in an example to make sure you fully understand the principles of using sendKeys(). For example, the following simulates a user selecting File | Open | Table, typing Customer, and pressing enter. Type in the fourth line into the pushButton event of a button.
Use sendKeys as a last resort because sendKeys is and has never been super reliable. This is true not just for Paradox but also for other environments such as Access and VB Classic. You almost always can find another way to accomplish the task.
In place of {ENTER}, you can use a tilde ~.
Use a False value for the optional wait parameter. Windows sometimes stops responding when the wait parameter is set to true (from the ObjectPAL help). The default is True so it is highly recommended to use False.
You may want to try {delay 100} at the beginning of your string to tell ObjectPAL to delay a 10th of a second "between" keystrokes.
If you are using Paradox 9, make sure you upgrade to SP3 or SP4. Sendkeys does not work on at least the initial developer edition.
I have found that the {delay 50} is required at least for Windows 7 64bit.
Refer to the ObjectPAL Reference help file for more information.
Vista / Windows 7 and sendKeys
SendKeys is a Windows feature that Paradox, Access, VB Classic and other older development environments make available with a programming command. Starting with Vista, Microsoft has disabled SendKeys by default -- a security precaution against hackers. Also, many antivirus programs will enforce the disabling of SendKeys. So, to use SendKeys going forward, you have to disable UAC and make sure your antivirus program does not enforce the disabling of SendKeys.
Summary
Trapping for keystrokes is done in either the keyChar or keyPhysical events. The keyPhysical event passes the eventInfo to keyChar only if a printable character was pressed. The best location to trap for keys is either directly on the field in question or at the form’s prefilter. If you wish to create a generic keyboard trapping routine, consider passing the eventInfo in the form’s prefilter to a library routine that processes it.
Dear Sirs
I am surprised. I am starting to change my old system to PDOX 9 and inmediatly arose the sendKeys() problem.
I am inserting also your lines with these topic: sendKeys ("%FOxxxx~") but nothing happens. I believe the problem is becouse sendkey() does not recognize the desktop as the active object.
How to solve ??
Thanks in advance
Regards
Wilhelm Leskovsek - Chile
wleskovsek@vtr.net
Lots of reasons why SendKeys might not work. Try the following tips:
Use sendKeys as a last resort because sendKeys is and has never been super reliable. This is true not just for Paradox but also for other environments such as Access and VB Classic. You almost always can find another way to accomplish the task.
In place of {ENTER}, you can use a tilde ~.
Use a False value for the optional wait parameter. Windows sometimes stops responding when the wait parameter is set to true (from the ObjectPAL help).