Yesterday I discovered a situation wherein a keyboard ShortCut did not fire when I was expecting it to.
The specific situation was: I pressed the ShortCut key combination for an Action of an ActionList on an MDI child, while a side bar on the MDI form was focussed.
I always was under the impression that ShortCuts would work globally. In exactly which circumstances do or do they not fire?
That's a deceptively simple question with a surprisingly long answer. First I will deal with some basics and then follow the ShortCut through the VCL code to finally arrive at - I hope - a satisfying conclusion.
What is a ShortCut?
A ShortCut represents a special keyboard combination of one or more keys that cause an operation. Special means special to the programmer who gives meaning to the specific key combination.
In Delphi a ShortCut is of type
TShortCutwhich is declared as a whole number within theWordrange (0..65535). A ShortCut is often constructed out of several keys, e.g.:CTRL+K =
scCtrl+Ord('K')=16384+75=16459.How to specify a ShortCut?
ShortCuts can be assigned to the
ShortCutorSecondaryShortCutsproperty of an Action or to theShortCutproperty of a MenuItem, thus invoking that Action'sOnExecuteevent or MenuItem'sOnClickevent when the ShortCut's keyboard combination is pressed.For an Action's ShortCut to be processed, it is required that the Action is enabled, and added to a not suspended ActionList or attached to an enabled MenuItem. Likewise, for a MenuItem's ShortCut to be processed, it is required that the MenuItem is added to a Menu.
ShortCuts can also be interpreted from the Application's, a Form's or from an ApplicationEvents'
OnShortCutevent. Within those events, theMsgparameter holds the keycode in itsCharCodemember, and possibly special keys like Shift, Ctrl or Alt can be extracted usingGetKeyState:If the
Handledparameter is set toTrue, any subsequent processing for the key will be skipped.How is a ShortCut catched?
The VCL does not keep a list of all specified ShortCuts. (How could it?). Thus all keystrokes could potentially be a ShortCut. And that is exactly how the VCL interprets ShortCuts: by evaluating every key that is pressed.
Peter Below wrote an excellent and comprehensive article dealing with A Key's Odyssey through the VCL. In short, a ShortCut is catched as follows:
TApplication.Runpicks up every Windows message send to the application,TApplication.ProcessMessagecallsIsKeyMsgwhich passes the message - if aWM_KEYDOWNmessage - on to theCN_KEYDOWNmessage handler of the focussed Control.How is the ShortCut processed?
TWinControl.CNKeyDownexamines whether the key is a menu key (we'll see the definition of this menu is beyond a physical Menu):TWinControl.IsMenuKeyfirst examines whether the key is a ShortCut within the Control's or one of its parent's PopupMenu,TMenu.IsShortCut1) traverses all its (sub)menu items and calls theOnClickevent handler of the enabled MenuItem with the ShortCut, if any,TCustomForm.IsShortCut2),OnShortCutevent of the Form is called, if assigned,FActionLists. That was considered a bug and as from BDS2006, the field was eliminated and the ActionLists could be indirectly owned by the Form too.TCustomActionList.IsShortCuttraverses all its Actions and callsHandleShortCutof the enabled Action with the ShortCut set in itsShortCutorSecondaryShortCutsproperty, if any,Application.IsShortCutis called (viaCM_APPKEYDOWN),OnShortCutevent of the Application is fired, this includesOnShortCutevents of all ApplicationEvents components within the project, if any assigned,IsShortCutroutine of the MainForm (see 2)), but only when the MainForm is enabled. E.g. when the active Form is a modal Form, then the MainForm will be disabled. This will fire theOnShortCutevent of the MainForm or will traverse all directly or indirectly owned ActionLists of the MainForm (depending on Delphi version as mentioned above).So, when is the ShortCut processed?
When it is:
OnShortCutevent of the currently active Form or of the MainForm, but only when the MainForm is enabled,OnShortCutevent of the Applicaton or any ApplicationEvents components.And when is the ShortCut not processed?
When it is set for: