Logo

Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.

KeyPress, KeyPreview

The KeyPress event lets you take action as soon as a user presses a key. When combined with the KeyPreview property, you can act at the form level when a key is pressed on any control.

Usage

PROCEDURE oObject.KeyPress
LPARAMETERS [ nControlIndex, ] nKeyPressed, nModifiers

Parameter

Value

Meaning

nControlIndex

Numeric

When a control is a member of a control array, nControlIndex is passed, indicating which control in the array received the keypress.

Omitted

The control receiving the keypress is not a member of a control array.

nKeyPressed

Numeric

The code for the key that was pressed. The codes passed by KeyPress are the same as those returned by INKEY().

nModifiers

Numeric

A code indicating whether any of Shift, Ctrl, or Alt were held down when the key was pressed. Each modifier has a value (Shift=1, Ctrl=2, Alt=4). nModifiers is the sum of the values for whichever of the modifier keys were held down.

Your KeyPress method code must accept these parameters. You only need the nControlIndex parameter in the KeyPress code if the method is for a group of controls contained in a control array. In that case, nControlIndex indicates which element of the array triggered the event (which of the controls received a key press).

In many cases, the value of nKeyPressed already reflects the modifier keys pressed, but there are a few keys that return the same value with at least some of the modifiers. The arrow keys, for instance, don’t distinguish between shifted and unshifted.

The really big plus of nModifiers is that it lets you know if multiple modifier keys are pressed. When you use more than one of Shift, Alt and Ctrl, you may be surprised by the value of nKeyPressed. If Alt is included, you always get the key code for the Alt version of the key, regardless of the condition of Ctrl and Shift. If Alt isn’t used, but Ctrl is (with or without Shift), you get the code for the Ctrl version of the key. Only when Shift and a key are used without either Alt or Ctrl do you get back the value for the shifted key. Finally, you get the unshifted key value only when the key is used without any of the modifiers.

This means that code which checks the character pressed must look like this:

IF INLIST(nKeyCode, 98, 66, 2, 48)  && Check for a "b"

Frankly, we think it would make more sense to always pass the unshifted value and let you figure it out with nModifiers. But the codes reflecting the modifier keys do make KeyPress work more like INKEY(), so it was probably done this way to make the transition to KeyPress easier.

KeyPress doesn’t catch keystrokes that are valid menu shortcuts. For example, with the default system menu, pressing Ctrl+A in a text box selects all the text in the box and doesn’t fire the KeyPress event.

Example

* To see how KeyPress works, try the following:
* Create a form in the Form Designer.
* Add a text box to the form.
* Put the following code in the KeyPress event of the text box:
WAIT WINDOW "Key = " + ALLTRIM(STR(nKeyCode)) + CHR(13) + ;
            "Modifier = " + ALLTRIM(STR(nShiftAltCtrl))
* Run the form and try typing different keys and key
* combinations into the textbox

Usage

oForm.KeyPreview = lValue
lValue = oForm.KeyPreview

KeyPreview is a form property that indicates whether all key presses should be routed to the form’s KeyPress method before the KeyPress method for the individual controls. When KeyPreview is true, the form’s KeyPress method is triggered whenever a key is pressed. This allows you to perform actions at the form level based on the keys pressed. Once the form’s KeyPress method finishes, the KeyPress method for the specific control fires.

Our favorite use for KeyPreview is to set a custom form property indicating that data has changed since the last save. The Save button can be enabled and disabled based on the custom property (and can reset the property after each save). Similarly, when the form is closed, you can display a message indicating data has changed if the property is true. While this technique, shown in the example below, isn’t foolproof (it doesn’t catch changes made with the mouse only), it is useful in forms where all data is entered from the keyboard and other controls are used only to perform actions on the data (save, cancel, etc.).

Another handy use for turning on KeyPreview: You can test for an Esc press and exit the form without having to have a Close button marked as the Cancel button. Just check for nKeyCode = 27.

A form's KeyPress event isn't fired, even with KeyPreview turned on, if an ActiveX control on the form has focus and it has a KeyPress method. This isn't a bug; it's just a fact of life when dealing with ActiveX controls, which have their own event models.

This one, on the other hand, is a bug: Custom code in KeyPress isn't executed for Alt-key combinations. If you turn event tracking on, you'll see that the KeyPress event fires, but the code in the KeyPress method doesn't execute. This is understandable for Alt-key combinations used as menu hot keys (such as Alt+F for the File menu)—which don't fire the KeyPress event at all—but not for unassigned combinations.

Example

* The form should have a property lChanged
* Set KeyPreview=.t.
* The form level KeyPress contains:
IF UPPER(this.ActiveControl.BaseClass) $ "TEXTBOX,EDITBOX" ;
   AND NOT INLIST(nKeyCode, 9, 13, 15, 271)
   * We're only interested in input keystrokes,
   * not keystrokes on buttons, etc.
   * We also only want to recognize "data" keystrokes,
   * so we omit Tabs, Returns, etc.
   * The list to omit could also include arrow keys and
   * other navigation keys.
   this.lChanged=.T.
ENDIF

* Now, the form's QueryUnload method
* can contain code like:
LOCAL nChoice
IF thisform.lChanged
   * Prompt the user to save changes.
   nChoice = MESSAGEBOX("Save changes", ;
               MB_YESNOCANCEL + MB_ICONQUESTION + MB_DEFBUTTON1)
   DO CASE
   CASE nChoice = IDYES
      * Save the current record.
   CASE nChoice = IDCANCEL
      * Kill the form close.
      NODEFAULT
   ENDCASE
ENDIF

See Also

InKey()