Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does a ShortCut fire?

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?

like image 946
NGLN Avatar asked Nov 22 '14 22:11

NGLN


1 Answers

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.
like image 101
NGLN Avatar answered Oct 04 '22 20:10

NGLN