Just a quick post to say Babak Yaghoobi – he of the open source native iOS control set – is coming up with the goods for Android too. The project – ‘D.P.F Delphi Android Native Components’ – is on SourceForge (http://sourceforge.net/projects/dpfdelphiandroid/). While it doesn’t look quite finished yet, he has made decent progress, so if you have XE5, it’s well worth a look.
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:
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:
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-entracy 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:
- 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:
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:
- 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:
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.
See the release notes here…
A bit ago I posted a QC report complaining about the speed at which a nested FMX popup menu unfurls. Steps:
- Create a new FMX desktop application.
- Add a TPopupMenu to the form, then four items to the popup menu, the second parented to the first, the third to second and the fourth to the third.
- Add a TRectangle to the form and assign its PopupMenu property to PopupMenu1.
- Right click on the popup menu, select the first item then hover over the second; once the third item is shown, hover over it to show the fourth.
Expected: the menu unfolds quickly.
Actual: the menu unfolds sloooowly.
At the time I thought FMX menus were just slow… until it recently got pointed out to me that the FMX source in fact hardcodes a delay of 500ms. As such, if you require quickly unfurling menus, just take a copy of FMX.Menus.pas and change the value assigned to FDelay in the constructor for TAutopopupMenuTimer (line 575 in XE5 RTM).
That said, ideally, the correct value would be got from the OS; for Windows, the API function to call is SystemParametersInfo:
uses WinApi.Windows; function GetMenuShowDelay: Integer; var Value: DWORD; begin SystemParametersInfo(SPI_GETMENUSHOWDELAY, 0, @Value, 0); Result := Value; end;
To be honest, I’m not sure what the OS X equivalent (if there is one) is, so if anyone knows, feel free to tell all in the comments…
A few weeks ago Marco Cantù, the Delphi Project Manager, blogged about a new FMX ShowModal overload introduced in XE5. As Marco explained, the reason for this new overload is because a Windows-style ShowModal isn’t possible on mobile platforms. While his blog post met the odd objection in the comments, if anything, a revised ShowModal could have been added from the start – due to deliberate design decisions by Apple, the classic ShowModal isn’t entirely straightforward even on OS X, so Embarcadero have my sympathies.
That said, I’ve found the actual implementation of the new overload problematic. On Windows and OS X, it fails to show the form modally at all (see QC 120024). Moreover, on all platforms, setting the shown form’s ModalResult property (either directly or by setting a button’s ModalResult property) does not close the form like it would do in the classic ShowModal style (QC 120025). To fix these issues, I would suggest Embarcadero do something like the following:
1. Move the ShowWindowModal method from IFMXWindowService into its own IFMXModalWindowService interface. If a platform doesn’t implement classic ShowModal functionality, then it shouldn’t implement the new interface.
2. Change the new ShowModal overload’s implementation to call the classic ShowModal if it is supported:
procedure TCommonCustomForm.ShowModal(const ResultProc: TProc<TModalResult>); begin if Supports(FWinService, IFMXModalWindowService) then ResultProc(ShowModal) else begin FResultProc := ResultProc; Show; end; end;
3. Amend TCommonCustomForm.SetModalResult to look something like this:
procedure TCommonCustomForm.SetModalResult(Value: TModalResult); begin FModalResult := Value; if Assigned(FResultProc) then begin Close; //!!!added FResultProc(FModalResult); FResultProc := nil; end; end;
4. Raise an exception in the classic ShowModal’s method body if IFMXModalWindowService isn’t implemented.
Just a small note to say, if anyone has downloaded my Android SharedPreferences wrapper (more info here), I’ve just put up a small fix for the ReadBool method, so you might want to update from the trunk. Thanks goes to Orren Grushkin for reporting the bug.
I’ve just realised Dave Nottage (TeamB, and now MVP) has a blog, currently focusing on Delphi for iOS – hopefully it will get syndicated by DelphiFeeds.com soon, but until then, check it out directly.