Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Ctrl+/ as shortcut in Delphi program

Is this even possible? Notepad++ for example does it, but simply trying to assign it to components like actions or menu items doesn't work. The event it is assigned to simply doesn't trigger.

So, I took my question to Google. Nada. Then, I tried stepping through the various shortcut functions, in this case TextToShortCut and ShortCutToText.

The first one, TextToShortCut, converts stuff like 'Ctrl+A' (a string) to the following 16bit value:

(uint)A | (uint)Ctrl

Works fine, mostly. However, I observed the following weirdness:

// Try converting back and forward...
TextToShortCut('Ctrl+/') = 16495

// That's incorrect. It should be:
Ord('/') or scCtrl = 16431

// Incorrect too
ShortCutToText(16495) = 'Ctrl+/'

// This is the shortcut the first line actually creates (Ctrl+o)
Ord('o') or scCtrl = 16495 // wut?

// Which is invalid, cause only caps are used
ShortCutToText(16431) = ''

What's going on here? For now, I believe the error lies in the final part of TextToShortCut: after handling the part before the + sign ("Ctrl" in this case), it will try to find a shortcut for the remaining part ("/"). However, in its current form the part after + must be a valid shortcut on its own too.

for Key := $08 to $255 do { Copy range from table in ShortCutToText }
  if AnsiCompareText(Text, ShortCutToText(Key)) = 0 then
  begin
    Result := Key or Shift;
    Exit;
  end;

So, because:

ShortCutToText('/') = 0 (failure)
MapVirtualKey('/',MAPVK_VK_TO_VSC) = 0 (failure)

... the loop fails to detect '/' as a valid shortcut.

Is this some VCL bug or am I missing something?

Here's a proof of concept (yes, I'm taking screenshots of code, but mashing this together with the Component Palette is faster than using this code directly):

Edit 1:

enter image description here

Edit 2:

Manually assigning 16431 to the menu item does not work.

like image 725
Orwell Avatar asked Sep 10 '13 16:09

Orwell


1 Answers

If you assign an OnShortCut event handler to your menu's parent TForm, you will see that pressing Ctrl+/ results in a TShortCut value of 16575 from Menus.ShortCutFromMessage(), which is the function that the VCL uses internally when dispatching keystrokes for shortcut handling.

Both ShortCutToText(16495) and ShortCutToText(16575) return 'Ctrl+/', so lets break it down:

scCtrl = $4000
16495 = $406F
16575 = $40BF

Both shortcuts have the scCtrl flag present.

ShortCutToText($6F) and ShortCutToText($BF) both return '/'. This is because MapVirtualKey() returns the same scan code ($350000) for both virtual keys $6F (VK_DIVIDE) and $BF (VK_OEM_2 - the /? key for US keyboards).

When dispatching a shortcut, the VCL does exact comparisons of TShortCut values. So when you have 16495 assigned as your shortcut, it will not trigger because the system is reporting a shortcut of 16575 instead, even though they both map to Ctrl+/.

When I assign 16575 to the ShortCut property of a TMenuItem or TAction, pressing Ctrl+/ triggers the item, as expected.

So the trick is that the system is reporting a shortcut that uses the VK_OEM_2 virtual key for /, but you are expecting it to use the VK_DIVIDE virtual key instead.

like image 85
Remy Lebeau Avatar answered Oct 21 '22 03:10

Remy Lebeau