Search code examples
delphikeyboard-shortcutsdelphi-10.4-sydney

How to get the string representation of a ShortCut Key including the SHIFTSTATE?


In a Delphi 10.4.2 Win32 VCL Application, and based on the question + solution here which provides a way to get the string representation of a Shortcut Key (but presumably with no possibility to also pass a SHIFTSTATE for the Shortcut Key) I wrote this code:

function MyGetSpecialShortcutName(ShortCut: TShortCut): string;
// gets shortcut name for e.g. VK_NUMPAD0 where TMenuItem.Shortcut gets the wrong shortcut name
var
  ScanCode: Integer;
  KeyName: array[0..255] of Char;
begin
  Result := '';
  FillChar(KeyName, SizeOf(KeyName), 0);
  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;

function GetSpecialShortcutNameWithShiftState(const AScanCode: Word; const AShiftState: System.Classes.TShiftState = []): string;
begin
  Result := MyGetSpecialShortcutName(Vcl.Menus.ShortCut(AScanCode, AShiftState));
end;

Usage:

Result := GetSpecialShortcutNameWithShiftState(VK_A, [ssCTRL]);

However, the Result is "A" where the expected Result should be "CTRL+A".

How to get the string representation of a ShortCut Key including the SHIFTSTATE?


Solution

  • The OP wants the key names fully localised, but for completeness I first show that the VCL already has a function to obtain a partly unlocalised string, namely, ShortCutToText in the Menus unit:

    ShortCutToText(ShortCut(Ord('A'), [ssShift, ssAlt]))
    

    This returns Shift+Alt+A on all systems.

    Now, using the Win32 function GetKeyNameText already mentioned in the Q, it is easy to obtain a fully localised shortcut string:

    function GetKeyName(AKey: Integer): string;
    var
      name: array[0..128] of Char;
    begin
      FillChar(name, SizeOf(name), 0);
      GetKeyNameText(MapVirtualKey(AKey, 0) shl 16, @name[0], Length(name));
      Result := name;
    end;
    
    function ModifierVirtualKey(AModifier: Integer): Integer;
    begin
      case AModifier of
        Ord(ssShift):
          Result := VK_SHIFT;
        Ord(ssCtrl):
          Result := VK_CONTROL;
        Ord(ssAlt):
          Result := VK_MENU;
      else
        Result := 0;
      end;
    end;
    
    function ShortcutToString(AKey: Integer; AShiftState: TShiftState = []): string;
    begin
      Result := '';
      for var Modifier in AShiftState do
      begin
        var ModifierKey := ModifierVirtualKey(Ord(Modifier));
        if ModifierKey <> 0 then
          Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(ModifierKey);
      end;
      Result := Result + IfThen(not Result.IsEmpty, '+') + GetKeyName(AKey);
    end;
    

    (Here I use a IfThen overload from StrUtils.)

    Now,

    ShortcutToString(Ord('A'), [ssShift, ssAlt])
    

    returns SKIFT+ALT+A on my Swedish system. SKIFT is, as you might already have guessed, the Swedish name for the SHIFT key.