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 TShortCut
which is declared as a whole number within the Word
range (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 ShortCut
or SecondaryShortCuts
property of an Action or to the ShortCut
property of a MenuItem, thus invoking that Action's OnExecute
event or MenuItem's OnClick
event 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' OnShortCut
event. Within those events, the Msg
parameter holds the keycode in its CharCode
member, and possibly special keys like Shift, Ctrl or Alt can be extracted using GetKeyState
:
procedure TForm1.FormShortCut(var Msg: TWMKey; var Handled: Boolean);
begin
if (Msg.CharCode = Ord('K')) and (GetKeyState(VK_CONTROL) < 0) then
begin
Caption := 'CTRL+K pressed';
Handled := True;
end;
end;
If the Handled
parameter is set to True
, 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.Run
picks up every Windows message send to the application,
TApplication.ProcessMessage
calls IsKeyMsg
which passes the message - if a WM_KEYDOWN
message - on to the CN_KEYDOWN
message handler of the focussed Control.
How is the ShortCut processed?
TWinControl.CNKeyDown
examines whether the key is a menu key (we'll see the definition of this menu is beyond a physical Menu):
TWinControl.IsMenuKey
first examines whether the key is a ShortCut within the Control's or one of its parent's PopupMenu,
TMenu.IsShortCut
1) traverses all its (sub)menu items and calls the OnClick
event handler of the enabled MenuItem with the ShortCut, if any,
- If not processed, it examines whether the key is a ShortCut of the Form on which the Control resides by calling
TCustomForm.IsShortCut
2),
- The
OnShortCut
event of the Form is called, if assigned,
- If not processed, it examines whether the key is a ShortCut within the Form's MainMenu (see 1)), if any,
- If not processed, it dispatches the key to all ActionLists owned by the Form (still talking about the active Form). Prior to Delphi version 10 (BDS2006), these ActionLists need to be directly owned by the Form and were kept in a protected (which made intervention possible when needed) field
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.IsShortCut
traverses all its Actions and calls HandleShortCut
of the enabled Action with the ShortCut set in its ShortCut
or SecondaryShortCuts
property, if any,
- If not processed,
Application.IsShortCut
is called (via CM_APPKEYDOWN
),
- The
OnShortCut
event of the Application is fired, this includes OnShortCut
events of all ApplicationEvents components within the project, if any assigned,
- If not processed, it calls the
IsShortCut
routine 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 the OnShortCut
event 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:
- Set for an enabled MenuItem in a PopupMenu which is attached to the currently focussed Control or to any of its parents,
- Set for an enabled MenuItem in a MainMenu which is shown on the currently active Form or on the MainForm, but only when the MainForm is enabled,
- Set for an enabled Action in a not suspended ActionList which is owned by the currently active Form or by the MainForm, but only when the MainForm is enabled,
- Catched in the
OnShortCut
event of the currently active Form or of the MainForm, but only when the MainForm is enabled,
- Catched in the
OnShortCut
event of the Applicaton or any ApplicationEvents components.
And when is the ShortCut not processed?
When it is set for:
- A disabled MenuItem,
- A MenuItem without Menu,
- A MenuItem in a MainMenu that is not attached to a Form,
- A MenuItem in a PopupMenu that is attached to a sibling,
- A disabled Action,
- An Action without ActionList,
- An Action in an ActionList that is not owned by the currently active Form or the MainForm. E.g.: another Form, a DataModule, Application, a utilities unit, etc...
- An Action in an ActionList that is not directly owned by the currently active Form or the MainForm in Delphi versions below BDS2006.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…