Search code examples
delphikeyboard-shortcutsmenuitemdelphi-10.4-sydney

How to show "NUMERIC KEYPAD" for menu item shortcut assigned with `VK_NUMPAD0`?


In a Delphi 10.4.2 Win32 VCL Application, on Windows10 X64 (German language) I set the shortcuts for some menu items programmatically:

mRasterizedDoubleSize.Shortcut := VK_ADD;
mRasterizedHalveSize.Shortcut := VK_SUBTRACT;
mRasterizedResetToOriginalSVGSize.Shortcut := VK_NUMPAD0;

This results in the following menu at run-time:

enter image description here

("ZEHNERTASTATUR" is German for NUMERIC KEYPAD)

Why "Zehnertastatur" (numeric keypad) is not shown for the third menu item?

How can I show "ZEHNERTASTATUR" (NUMERIC KEYPAD) for the menu item shortcut assigned with VK_NUMPAD0?

I have filed a Quality Report for this bug in Vcl.Menus: https://quality.embarcadero.com/browse/RSP-33296 Please vote for it!

EDIT: I have tried Andreas' advice, but it does work only programmatically at run-time, not at design-time in the Object Inspector:

mRasterizedResetToOriginalSVGSize.Caption := mRasterizedResetToOriginalSVGSize.Caption + #9 + '0 (NUMPAD)  ';

enter image description here

Isn't there a function that translates the word "NUMPAD" into the current system language at-run-time?

EDIT2: I have tried this to get the word for the VK_NUMPAD0 shortcut, but it only gives back the same "0" without the "NUMPAD" suffix:

var s: TShortCut;
s := ShortCut(VK_NUMPAD0, []);
CodeSite.Send('TformMain.FormCreate: ShortCutToText(s)', ShortCutToText(s));

enter image description here

EDIT3: I now have debugged Vcl.Menus: The bug seems to be in Vcl.Menus.ShortCutToText: While VK_ADD ($6B) is correctly translated by GetSpecialName(ShortCut), VK_NUMPAD0 ($60) is NOT being translated by GetSpecialName(ShortCut)!

EDIT4: I have found the solution:

function MyGetSpecialName(ShortCut: TShortCut): string;
var
  ScanCode: Integer;
  KeyName: array[0..255] of Char;
begin
  Result := '';
  ScanCode := Winapi.Windows.MapVirtualKey(LoByte(Word(ShortCut)), 0) shl 16;
  if ScanCode <> 0 then
  begin
    if Winapi.Windows.GetKeyNameText(ScanCode, KeyName, Length(KeyName)) <> 0 then
      Result := KeyName;
  end;
end;

var s: System.Classes.TShortCut;
s := ShortCut(VK_NUMPAD0, []);
CodeSite.Send('ShortCutToText', MyGetSpecialName(s));

enter image description here


Solution

  • One approach is like this:

    Use a TActionList. This is good practice in general. Define your actions within this list, and then simply map them to menu items, buttons, check boxes, etc. The action list facility is one of the very best parts of the VCL IMHO.

    Now, create an action named aResetZoom with Caption = 'Reset zoom'#9'Numpad 0' and NO ShortCut. Put this on the menu bar.

    Then, create a new action named aResetZoomShortcut with the same OnExecute (and possibly the same OnUpdate) and shortcut Num 0 (set at design time or programmatically at run time). Don't put this on the main menu.

    The result:

    Screenshot of main form with menu item View / Reset zoom shown. To the right of the menu item is the text "Numpad 0".

    and the action is triggered when I press numpad 0 (but not the alpha 0).

    There are many variants to this approach. Maybe you can make it work with a single action with no ShortCut but with Num 0 in its SecondaryShortCuts list. Or you can use the form's KeyPreview and OnKeyPress properties instead of the "dummy" action.

    Many options. Choose the one that is best suited for your particular scenario.

    Bonus remarks

    Please note it is perfectly possibly to set captions with tabs at design time using the Object Inspector. See example video.

    You can probably do localisation using the Win32 GetKeyNameText function. The following code is adapted from the VCL:

    var
      name: array[0..128] of Char;
    begin
      FillChar(name, SizeOf(name), 0);
      GetKeyNameText(MapVirtualKey(VK_NUMPAD0, 0) shl 16, @name[0], Length(name));
      // string(name) now is 'NUM 0' on my system
    

    That being said, personally I don't mind if shortcut descriptions are non-localized or manually localised -- like the rest of the application.

    Update

    A clarification on how to use the localisation code:

    procedure TForm5.FormCreate(Sender: TObject);
    var
      name: array[0..128] of Char;
      NameAsANormalString: string;
    begin
      FillChar(name, SizeOf(name), 0);
      GetKeyNameText(MapVirtualKey(VK_NUMPAD0, 0) shl 16, @name[0], Length(name));
      NameAsANormalString := name;
      ShowMessage(name);
    end;
    

    produces

    Message box with text "NUM 0"

    on my system.