4

In a Delphi 10.4.2 Win32 VCL Application in Windows 10 x64, I use this code to programmatically create a Shortcut STRING for the DELETE key on a popup menu item:

mGalleryDeleteSelected.Caption := mGalleryDeleteSelected.Caption + #9 + MyShortcutToString(VK_DELETE, []) + ' ';

This is the source-code:

function MyGetKeyName(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 MyModifierVirtualKey(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 MyShortcutToString(AKey: Integer; AShiftState: TShiftState = []): string;
begin
  Result := '';
  for var Modifier in AShiftState do
  begin
    var ModifierKey := MyModifierVirtualKey(Ord(Modifier));
    if ModifierKey <> 0 then
      Result := Result + IfThen(not Result.IsEmpty, '+') + MyGetKeyName(ModifierKey);
  end;
  Result := Result + IfThen(not Result.IsEmpty, '+') + MyGetKeyName(AKey);
end;

However, instead of getting the shortcut string for the normal DELETE key, I get the shortcut string for the (auxiliary?) Comma/Delete key on the Numeric keypad:

enter image description here

As I have a German language Windows, here are the translations:

KOMMA = comma
ZEHNERTASTATUR = numeric keypad
The default Delete key on a German keyboard has the label "entf" (abbreviation for "Entfernen")

The keypress of the comma key on the numeric keypad does work as a Delete command if the numeric keypad is set to work for navigation commands. However, I need to get the string for the NORMAL DELETE key. How can I do that?

user1580348
  • 5,721
  • 4
  • 43
  • 105

1 Answers1

7

From the documentation for GetKeyNameText:

24 Extended-key flag. Distinguishes some keys on an enhanced keyboard.

This says that it uses bit 24 in the LPARAM-styled argument as the extended-key flag.

Then, in About Keyboard Input:

Extended-Key Flag

The extended-key flag indicates whether the keystroke message originated from one of the additional keys on the enhanced keyboard. The extended keys consist of the ALT and CTRL keys on the right-hand side of the keyboard; the INS, DEL, HOME, END, PAGE UP, PAGE DOWN, and arrow keys in the clusters to the left of the numeric keypad; the NUM LOCK key; the BREAK (CTRL+PAUSE) key; the PRINT SCRN key; and the divide (/) and ENTER keys in the numeric keypad. The extended-key flag is set if the key is an extended key.

(body text emphasis mine).

Therefore, I conclude, that you can use

function MyGetKeyName(AKey: Integer): string;
var
  name: array[0..128] of Char;
begin
  FillChar(name, SizeOf(name), 0);             // ADD THIS! 
  GetKeyNameText(MapVirtualKey(AKey, 0) shl 16 or 1 shl 24, @name[0], Length(name));
  Result := name;
end;

to get the name of the Del key between the alphabetical and numeric parts of the keyboard.

Of course, you probably don't want to set this bit for other keys, so you need to refactor your code. Unfortunately, it seems like VK_DELETE can map to two physical keys.

Here's one approach:

function MyGetKeyName(AKey: Integer; AExtended: Boolean = False): string;
var
  name: array[0..128] of Char;
begin
  FillChar(name, SizeOf(name), 0);
  if                                             // ADD THIS! 
    GetKeyNameText(MapVirtualKey(AKey, 0) shl 16 or Cardinal(Ord(AExtended)) shl 24,
      @name[0], Length(name)) <> 0
  then
    Result := name
  else
    Result := '';
end;

and add this parameter to your MyShortcutToString as well (which only need to pass it to MyGetKeyName):

function MyShortcutToString(AKey: Integer; AShiftState: TShiftState = [];
  AExtended: Boolean = False): string;
begin
  Result := '';
  for var Modifier in AShiftState do
  begin
    var ModifierKey := MyModifierVirtualKey(Ord(Modifier));
    if ModifierKey <> 0 then
      Result := Result + IfThen(not Result.IsEmpty, '+') + MyGetKeyName(ModifierKey);
  end;
  Result := Result + IfThen(not Result.IsEmpty, '+') + MyGetKeyName(AKey, AExtended);
end;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • 1
    As always, your solutions are ingenious and well explained! It is amazing how many extended bits can be put into a keyboard that has only 105 keys! In the next few days, I will meditate on every bit and see whether I will get enlightened. Thanks! – user1580348 Jun 12 '21 at 21:41
  • BTW, here the compiler spits out a warning about combining signed and unsigned types (widened both operands): `GetKeyNameText(MapVirtualKey(AKey, 0) shl 16 or Ord(AExtended) shl 24, @name[0], Length(name));` – user1580348 Jun 12 '21 at 21:55
  • You should check the return value of GetKeyNameText for error. I don't think you need to zero fill the array either. – David Heffernan Jun 14 '21 at 08:09
  • @DavidHeffernan: I suspect `GetKeyNameText` will not touch the buffer (or set it to the empty string) if an error occurs, in which case the function quite reasonably will return the empty string, but you are right: this isn't explicitly documented and you should always check the return value. Regarding zeroing, I consider that best practice but agree it probably isn't necessary in this case. But of course, as the code is currently written, the zeroing *is* needed if `GetKeyNameText` fails. – Andreas Rejbrand Jun 14 '21 at 08:18
  • Yeah, I'd not zero init and wrap the call in a Win32Check – David Heffernan Jun 14 '21 at 08:26
  • @DavidHeffernan: I don't think I agree with that approach, because then you will get an exception on failure. I think I prefer the function to return the empty string. For instance, what if your app supports customizable menus, and in some strange configuration in some "strange" OS language, `GetKeyNameText` fails when you open the `Tools` main menu. Do you want an exception (effectively making it impossible to open the menu) or do you want the shortcut text to the right to be empty? But of course, I do agree with the "check retval" part. – Andreas Rejbrand Jun 14 '21 at 08:31
  • No that's fair enough – David Heffernan Jun 14 '21 at 08:38