Adding a shortcut to a programmatically added system menu option
Asked Answered
S

1

8

In my application, I have a base form in which various items are added to the system menu, for example

 AppendMenu (SysMenu, MF_SEPARATOR, 0, '');
 AppendMenu (SysMenu, MF_STRING, SC_Sticky, 'Sticky');
 AppendMenu (SysMenu, MF_STRING, SC_Original, 'Original');

How does one add keyboard shortcuts to these menu options (eg Alt-F2, Alt-F3)?

I can't use the standard method of using an accelerator (ie &Sticky for Alt-S) as the real menu captions are in Hebrew and accelerators don't seem to work properly with this language.

Savoirvivre answered 30/9, 2012 at 6:52 Comment(7)
Check the code under TMenuItem.Shortcut or TMenuItem.SetShortcut. It probably has what you need (can't do it myself because i have not Delphi at hand)Bookstack
You are looking for InsertMenuItemTetanize
@DavidHeffernan: The fourth parameter to InsertMenuItem is a pointer to a MENUITEMINFO structure that contains information about the new menu item. But this structure does not contain (as far as I can see) any information about shortcuts.Peck
You're right. I misremembered. Sorry.Tetanize
Eek. Looks seriously old school. Have to add the shortcut text to the caption with a #9 separator. And then handle the shortcut invocation in a message based way. Non-trivial.Tetanize
@DavidHeffernan: When you write 'handle the shortcut invocation in a message based way', which message would this be? I'm currently handling WMSysCommands (my commands are defined as WM_USER + n) but it looks like I'll have to handle something else.Peck
@No'am - There're no other messages involved, a menu item in the system menu posts a WM_SYSCOMMAND. What you're missing is an accelerator for your menu item.Dolorisdolorita
D
7

Here's an example that uses an accelerator table:

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AppEvnts;

type
  TForm1 = class(TForm)
    ApplicationEvents1: TApplicationEvents;
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
    procedure FormDestroy(Sender: TObject);
  private
    FAccelTable: HACCEL;
    FAccels: array[0..1] of TAccel;
  protected
    procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const
  SC_Sticky = 170;
  SC_Original = 180;

procedure TForm1.FormCreate(Sender: TObject);
var
  SysMenu: HMENU;
begin
 SysMenu := GetSystemMenu(Handle, False);
 AppendMenu (SysMenu, MF_SEPARATOR, 0, '');
 AppendMenu (SysMenu, MF_STRING, SC_Sticky, 'Sticky'#9'Alt+F2');
 AppendMenu (SysMenu, MF_STRING, SC_Original, 'Original'#9'Alt+F3');

 FAccels[0].fVirt := FALT or FVIRTKEY;
 FAccels[0].key := VK_F2;
 FAccels[0].cmd := SC_Sticky;
 FAccels[1].fVirt := FALT or FVIRTKEY;
 FAccels[1].key := VK_F3;
 FAccels[1].cmd := SC_Original;

 FAccelTable := CreateAcceleratorTable(FAccels, 2);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DestroyAcceleratorTable(FAccelTable);
end;

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  TranslateAccelerator(Handle, FAccelTable, Msg);
  inherited;
end;

procedure TForm1.WMSysCommand(var Message: TWMSysCommand);
begin
  inherited;
  case Message.CmdType of
    SC_Sticky: ShowMessage('sticky');
    SC_Original: ShowMessage('original');
  end;
end;
Dolorisdolorita answered 30/9, 2012 at 9:43 Comment(5)
Fantastic! Despite the fact that I've been googling all morning and checking my Delphi (and Windows) books, I've never seen an accelerator table before. This is SO at its best - everybody can learn something!Peck
This works well. However, some applications add a custom menu item to the system menu of ALL WINDOWS - not only the own application (probably with the help of a hook). I would give a lot for being able to do that.Imbrication
@Imbrication Cant this be achieved by querying the visible windows, and then loop over all these window handles and add the menu just like here above? Here we use the form's handle, but this should also work for other windows..Melodramatize
@Melodramatize Yes, adding an item to any window's system menu is easy—just use the window's handle—but how do you get notified when the user clicks on an added system menu item?Imbrication
Therefore you will need a window hook. I created one of these a couple of years ago (well more than a couple) for MSN Messenger, i created my own MSN messenger extension which hooked to MSN messenger main window and conversation windows. You would need such a hook for every window you want to add the menu to..Melodramatize

© 2022 - 2024 — McMap. All rights reserved.