Working around an XE5/Mac menu issue

In XE5, if you try running the FMX ‘ActionsDemo’ sample project on OS X, you’ll quickly find an issue – the menu bar isn’t set up correctly:

ActionsDemo menu wrong
ActionsDemo menu wrong 2

ActionsDemo menu wrong 3
If you open up the menu designer in the IDE, you’ll find what should be happening is that the application menu gets four items: Hide ActionsDemo, Hide Others, a separator, and Quit ActionsDemo, with all but the separator having an appropriate shortcut assigned:

ActionsDemo menu designer
Each of the three substantive items uses a standard FMX action, and to be honest, I have always found them poorly coded since they were introduced in XE3. However, the menu bar not being built correctly at all didn’t happen in XE3.

Comparing the FMX Mac menu bar source code between XE3 and XE5 doesn’t come up with much though – the code for building the default menu bar has been split out into its own method, and that’s about it. However, after adding a couple of breakpoints and running the ActionsDemo sample through the debugger, I discovered the key method – TPlatformCocoa.CreateOSMenu – being called several times when the application was starting up. Partly this was due to style notifications, and partly due to the code being re-entrant in a somewhat sloppy way – TPlatformCocoa.CreateOSMenu first gets rid of what was there before, however the clean-up code for a FMX menu item on OS X religiously calls CreateOSMenu to rebuild the menubar regardless of whether the item is being destroyed singularly, or as part of a general ‘get rid of everything’ call. Since the CreateOSMenu code in itself doesn’t take account of possible re-entrancy (i.e., being called again when it hasn’t yet finished the time before)… the end result is not well determined.

As such, a quick fix for the re-entrancy issue is to add a flag to avoid CreateOSMenu doing its stuff when it hasn’t finished doing it before:

  • Take a copy of FMX.Platform.Mac.pas
  • In the copy, add the following private field to TPlatformCocoa:
FCreatingOSMenu: Boolean;
  • Amend the top of TPlatformCocoa.CreateOSMenu to look like this:
if FCreatingOSMenu then Exit; //!!!added
AutoReleasePool := TNSAutoreleasePool.Create;
try
  FCreatingOSMenu := True;    //!!added
  • Amend the bottom of TPlatformCocoa.CreateOSMenu to look like this:
finally
  FCreatingOSMenu := False;    //!!!added
  AutoReleasePool.release;
end;
  • Save the unit, add it to the project, and rebuild.

While this fix gets rid of the MacOSMenuItem top level item that shouldn’t be there, things still aren’t OK though:

ActionsDemo menu still wrong

Notice the application menu is still missing Hide ActionsDemo and Hide Others. This is because the action classes used – TFileHideApp and TFileHideAppOthers – are poorly coded. Happily, they can be avoided easily enough though, and without requiring platform specific code as such:

  • Add the following private field to the form:
FHideAppService: IFMXHideAppService;
  • In the form’s OnCreate event handler, retrieve the platform service in the normal way:
FHideAppService := TPlatformServices.Current.GetPlatformService(
  IFMXHideAppService) as IFMXHideAppService;
  • Bring up the form’s designer surface, right click on it and select View as Text.
  • Find TFileHideApp and TFileHideAppOthers at the end, and change the types to plain TAction.
  • Right click and choose View as Form.
  • Double click the action list, and in the action list editor, find and put back the two actions’ standard Text and ShortCut properties – the Text for FileHideApp1 should be ‘Hide ActionsDemo’ and the ShortCut Cmd+H; the Text for FileHideAppOthers1 should be ‘Hide Others’ and the ShortCut Cmd+Alt+H.
  • Handle FileHideAppOthers1’s OnExecute event like this:
procedure TMainForm.FileHideAppOthers1Execute(Sender: TObject);
begin
  FHideAppService.HideOthers;
end;
  • Handle FileHideApp1’s OnUpdate event like this (in practice the code isn’t necessary for how the demo is set up – SDI not Mac-style MDI – but we’ll add it for completeness):
procedure TMainForm.FileHideApp1Update(Sender: TObject);
begin
  (Sender as TCustomAction).Enabled := not FHideAppService.Hidden;
end;
  • Handle FileHideApp1’s OnExecute event like this:
procedure TMainForm.FileHideApp1Update(Sender: TObject);
begin
  FHideAppService.Hidden := True;
end;
  • On saving (Ctrl+S), you’ll be prompted to update two type declarations; answer Yes to both. Re-run the demo, and the menu bar should now be set up correctly:

ActionsDemo menu fixed

PS (and changing the subject) – for those who contacted me a bit ago about my Exif code, sorry, I will get round to replying, but I’ve just been busy with other things recently.

Advertisements

5 thoughts on “Working around an XE5/Mac menu issue

  1. Chris,
    thank you so much for this. You’d think Embarcadero could have fixed this by now, but apparently they consider it to be “resolved” because you have posted this workaround. Glad you did.
    Scott

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s