1
votes

I am working with Delphi XE7 in Windows 10. I have a TMainMenuBar on both a primary form and a modal secondary form. The problem is that the accelerator keys on the secondary form do not activate the menu if the secondary form also contains a TMemo. For example, if the secondary form has a File menu, Alt+F does not open the file menu. However, if alt is pressed and released, "File" is highlighted and "F" is underlined and pressing F will open the menu. Note that the problem disappears if the TMemo is removed. Also, a Tmemo on the primary form does not cause problems with the menu on the primary form.

I have searched on "TActionMainMenuBar Accelerator Keys on Secondary Form" but none of the hits describe this particular problem, although other issues with this component and accelerator keys are discussed. Does anyone know how to achieve the desired behavior while still using TActionMainMenuBar on both forms? (I prefer to not use a standard TMenu for various reasons.)

1
Are you sure it has to do with whether the memo exists? I'm willing to bet it depends on whether the memo has focus.Jerry Dodge
@JerryDodge you are correct that the problem is not the TMemo per se but the presence of a control that can take focus. For example, I can delete the TMemo and get the same result with a combobox or radiogroup on the form. The problem disappears if the memo, combobox or radiogroup is disabled.TomT

1 Answers

5
votes

This is a VCL design issue. (Below explanation maybe somewhat off in parts. I'm tracing with XE2 and the behavior is not exactly equivalent. You may need to leave off one of the message handlers at the solution part.)

Menu bar accelerators generate a WM_SYSCOMMAND message. Your exact accelerator case is given as an example in API documentation:

If the wParam is SC_KEYMENU, lParam contains the character code of the key that is used with the ALT key to display the popup menu. For example, pressing ALT+F to display the File popup will cause a WM_SYSCOMMAND with wParam equal to SC_KEYMENU and lParam equal to 'f'.

Action menu bars are proprietary VCL components. As such default window procedure of a form have no chance to handle an accelerator message for them. The component itself have the code to simulate the behavior (TCustomActionMainMenuBar.WMSysCommand), but it has to be delivered the message to be able to do that. VCL's design issue is that, only an action menu bar on the main form is given that opportunity.

A TWinControl receiving a WM_SYSCOMMAND (secondary form itself or the memo in this case), makes its parent form (secondary form) perform a CM_APPSYSCOMMAND. Upon receiving the message, the form (again the secondary form) sends the message to the Application window. CM_APPSYSCOMMAND handler of the Application, transforms the message again to a WM_SYSCOMMAND and sends it to the main form.

I can only guess, but the purpose behind this design can be, to be able to access the main menu from secondary forms that don't have menu bars.

In any case, short of switching to native menus, you have to intercept and forward the message to the action menu bar on the secondary form before the message is processed by the VCL. There can be other ways to accomplish this, but what I tried and seemed to work is this:

type
  TSecondaryForm = class(TForm)
    ...
  protected
    procedure CMAppsyscommand(var Message: TMessage); message CM_APPSYSCOMMAND;
    procedure WMSysCommand(var Message: TWMSysCommand); message WM_SYSCOMMAND;
    ...


procedure TSecondaryForm.CMAppsyscommand(var Message: TMessage);
begin
  if ActionMainMenuBar1.Perform(PMessage(Message.LParam).Msg,,
      PMessage(Message.LParam).WParam, PMessage(Message.LParam).LParam) = 0 then
    inherited;
end;

// you may not need the below handler
procedure TSecondaryForm.WMSysCommand(var Message: TWMSysCommand);
begin
  if ActionMainMenuBar1.Perform(Message.Msg,
      TMessage(Message).WParam, TMessage(Message).LParam) = 0 then
    inherited;
end;