0

Put TMainMenu on the form and attach this handler to some TMenuItem. It will hide the main menu, but not if Custom Vcl Style is active.

procedure TForm1.HideMenuClick(Sender: TObject);
begin
  Menu := nil;
  { raised exception class $C0000005 with message 'access violation at 0x0038f397: read of address 0x00000008'. }
end;

This will work, but only if the delay is large enough:

procedure TForm1.HideMenuClick(Sender: TObject);
begin
  TThread.ForceQueue(nil, procedure   
  begin
    Menu := nil
  end,
  20);
end;  

Is there a clean and reliable way to do this?

(Note: The question is only about TMainMenu and its OnClick events, not about other ways to remove the menu or other controls.)

Edit:

Use the mouse to reproduce the problem. If you use the keyboard to open the menu and activate the item, then single 'ForceQueue(nil, procedure begin Menu := nil; end)' will be enough to avoid access violation error. If the menu item is activated with a keyboard shortcut, then even simple 'Menu := nil' will work.

A. Bonic
  • 107
  • 8
  • Is it really worth it, to make your app less functional, less accessible, and less robust, only to get a non-standard UI? – Andreas Rejbrand Apr 29 '23 at 11:47
  • @Andreas Rejbrand My intention is to add a fairly standard dark mode to an existing app. Hence the use of VCL Styles. As for hiding and revealing menus as needed, that seems to me to be a pretty standard thing nowadays as well. – A. Bonic Apr 29 '23 at 12:20
  • Sure. Hiding the menu bar is not a problem in a Win32 app. But VCL styles is not a native Win32 thing and it is very buggy. Classically, Win32 apps don't have "dark mode", but of course I know such modes are popular today. I personally think quality, stability, bugfreeness, and accessibility is much more important than style, so I'd either not do a "dark mode" or do it myself, without VCL styles. – Andreas Rejbrand Apr 29 '23 at 12:32
  • 1
    @A.Bonic "*Is there a clean and reliable way to do this?*" - you already found it. Delay setting the `Menu` property until after the `OnClick` handler exits. The VCL still needs access to the menu item after the event exits, so the menu has to remain intact until control returns to the main message loop. – Remy Lebeau Apr 29 '23 at 14:58
  • @Remy Lebeau For example, a delay of 4ms is not enough in my VM. In some other environment maybe even 20ms will not always be enough. It is hardly reliable or clean. – A. Bonic Apr 29 '23 at 15:39
  • @A.Bonic You should not need any timeout at all. That would imply there is a timer element to the styling, and I seriously doubt that is the case. So I suggest you enable Debug DCUs and step into the VCL source code to find out what is really happening. – Remy Lebeau Apr 29 '23 at 16:23
  • 1
    FWIW, I can reproduce the AV in 10.4 (with a "naïve" `Menu := nil`) but for me `TThread.ForceQueue(nil, procedure begin Menu := nil end);` appears to work. – Andreas Rejbrand Apr 29 '23 at 23:26

2 Answers2

0

No arbitrary timeout, and it seems error free, at least with Delphi 11.3. But I doubt anyone would call this a clean solution.

procedure TForm1.HideMenuClick(Sender: TObject);
begin
  TThread.ForceQueue(nil, procedure
  begin
    TThread.ForceQueue(nil, procedure
    begin
      Menu := nil;
    end);
  end);
end;
A. Bonic
  • 107
  • 8
0

This one works for me without using timers or threads:

type
  TForm150 = class(TForm)
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    HideMenu1: TMenuItem;
    procedure HideMenu1Click(Sender: TObject);
  private
    { Private declarations }
    PROCEDURE   HideMenu(VAR Msg : TMsg); MESSAGE WM_USER+1;
  public
    { Public declarations }
  end;

var
  Form150: TForm150;

implementation

{$R *.dfm}

procedure TForm150.HideMenu(var Msg: TMsg);
begin
  Menu:=NIL
end;

procedure TForm150.HideMenu1Click(Sender: TObject);
begin
  PostMessage(Handle,WM_USER+1,0,0)
end;

PostMessage allows the TMenuItem.OnClick event to complete before attempting to hide the TMainMenu.

HeartWare
  • 7,464
  • 2
  • 26
  • 30
  • That was the first thing I tried. The error occurs in the same place as with single ForceQueue without additional delay. `procedure TFormStyleHook.TMainMenuBarStyleHook.Invalidate; begin FFormHook.InvalidateNC; { FFormHook is nil here } end;` But since you're the second person who can't reproduce the whole problem, I'm starting to believe that there's something wrong with my setup (Delphi 11.3, Windows10 Dark Style). – A. Bonic May 04 '23 at 10:06
  • @A.Bonic: Just out of curiosity - can you reproduce the problem with the above code (and nothing else of your own code)? If so, then there must be something going on on your machine that doesn't happen on mine. I used Windows 10 Dark Style as well, and used the mouse to select the menu item. The menu (as you probably can see from the code) had a single "File" menu at the top with a "Hide Menu" item within that one. – HeartWare May 04 '23 at 12:15
  • That's just how I tested it. New project, just your code. Debug or Release configuration, test run in VM or outside VM, same error. – A. Bonic May 04 '23 at 13:31