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

FMX menu unfurling speed

A bit ago I posted a QC report complaining about the speed at which a nested FMX popup menu unfurls. Steps:

  1. Create a new FMX desktop application.
  2. 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.
  3. Add a TRectangle to the form and assign its PopupMenu property to PopupMenu1.
  4. 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…

FMX/XE5 issue: new ShowModal overload poorly implemented

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.

FMX issue – inability to safely process paint messages immediately

There are times when you want to repaint a control immediately, typically to give an indication to the user that something is actually happening during a lengthy bit of processing. In the VCL, this is covered by a troika of methods on TControl:

  • Invalidate posts a message to the event queue saying the control needs to be repainted.
  • Update forces processing of any paint messages pending for the control.
  • Repaint calls Invalidate and Update in succession.

That said, sometimes it isn’t even necessary to call one of these methods if the control wraps a native API widget, and the native API does something special behind the scenes. For example, try this:

  1. Create a new VCL application
  2. Add a TButton and a TProgressBar to the form
  3. Add the following handler for the button’s OnClick event:
uses
  System.Diagnostics;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Stopwatch: TStopwatch;
begin
  ProgressBar1.Position := ProgressBar1.Min;
  for I := ProgressBar1.Min + 1 to ProgressBar1.Max do
  begin
    Stopwatch := TStopwatch.StartNew;
    repeat
    until Stopwatch.ElapsedMilliseconds >= 50;
    ProgressBar1.Position := I;
  end;
end;

When I run this code, I find the progress bar still updating, if not entirely smoothly. Adding an Update call fixes that:

    ProgressBar1.Position := I;
    ProgressBar1.Update; //!!!added
  end;
end;

Turn to FireMonkey however, and this core functionality is missing:

  • While the FMX TControl has a Repaint method, it corresponds to the VCL Invalidate, not the VCL Repaint. (Oddly, the FMX TControl also has a InvalidateRect method to invalidate just a certain area of the control; given that, why confuse matters with the Repaint naming…?)
  • Since FMX to a large extent reimplements native widgets instead of wrapping them like the VCL prefers to do, niceties like the VCL progress bar doing its own updating don’t happen with the FMX equivalent.

To see this, try reimplementing the VCL demo presented above. In doing so the event handler will need to be tweaked a bit, mainly because the FMX TProgressBar uses Single not Integer values for its progress values:

uses
  System.Diagnostics;

procedure TForm2.Button1Click(Sender: TObject);
var
  Stopwatch: TStopwatch;
begin
  ProgressBar1.Value := ProgressBar1.Min;
  while ProgressBar1.Value < ProgressBar1.Max do
  begin
    Stopwatch := TStopwatch.StartNew;
    repeat
    until Stopwatch.ElapsedMilliseconds >= 50;
    ProgressBar1.Value := ProgressBar1.Value + 1;
  end;
end;

Run it, and you will find the progress bar visually updates in one big leap at the end. Adding a Repaint call doesn’t help:

    ProgressBar1.Value := ProgressBar1.Value + 1;
    ProgressBar1.Repaint; //!!!added
  end;
end;

What does ‘work’ is adding a call to Application.ProcessMessages instead. Is this a good idea however? Hardly: Application.ProcessMessages, as its name implies, processes all messages currently outstanding (paint and otherwise) for all controls (and if there are any other message-handling primitives in the application, those too), which can soon lead to serious re-entrancy issues.

Nonetheless, on Windows it is possible to process paint messages for a FMX form using something like the following code:

uses
  WinApi.Windows, FMX.Platform.Win;

procedure ProcessPaintMessages(const Form: TCustomForm);
begin
  UpdateWindow(WindowHandleToPlatform(Form.Handle).Wnd);
end;

This is XE4/5; for XE3 use the following (a stable API isn’t one of FMX’s strengths!):

uses
  WinApi.Windows, FMX.Platform.Win;

procedure ProcessPaintMessages(const Form: TCustomForm);
begin
  UpdateWindow(FmxHandleToHWND(Form.Handle));
end;

You have to do things at the form level since a FMX control on Windows isn’t backed by its own HWND. Further, on OS X, trying something similar appears to confuse the FMX styling system, so all in all… the situation isn’t great. For a recent QC report on the matter, see here: http://qc.embarcadero.com/wc/qcmain.aspx?d=119083. Unfortunately the person who mans QC hasn’t understood the point of it, and I wouldn’t be surprised if there are other similar reports on the system.

A few XE5-related bits

With XE5 now out, I’ve updated my FMX TClipboard and Mac PDF writer code to compile with it, and in the case of the latter, also made an unrelated fix suggested by Sebastian Zierer. With respect to TClipboard, I must add the caveat that the Android implementation merely delegates to the standard, frankly half-arsed IFMXClipboardService, and as such, only supports text. This is because my investigations into the Android API suggest there is no standard way to exchange pictures over the Android clipboard – if anyone knows something to the contrary, by all means let me know in the comments though. Anyhow, if you want to browse the code, check it out here: http://code.google.com/p/delphi-foundations/source/browse/#svn%2Ftrunk%2FFMX%20Utilities Alternatively, the SVN link is http://delphi-foundations.googlecode.com/svn/trunk/FMX%20Utilities/ I have also just posted to the same place a TCustomIniFile descendant that delegates to the Android SharedPreferences API, which I have unimaginatively called TAndroidPreferencesIniFile (the unit is CCR.PrefsIniFile.Android.pas). This class corresponds to the TMacPreferencesIniFile TApplePreferencesIniFile class I had written previously, which sits on top of the CFPreferences API on OS X and iOS. The idea in both cases is to have classes that roughly correspond to TRegistryIniFile on Windows, providing a consistent interface over whatever is the native preferences store for the platform. Be warned that unlike TIniFile (but like TApplePreferencesIniFile), TAndroidPreferencesIniFile is case sensitive with respect to section and key names. Further, there’s also a slightly annoying issue in which each time an app is deloyed by the IDE, existing preference data gets wiped in the process. Given when you run an Android app from the IDE you see a message saying it is uninstalling the previous version of the app, I imagine the preference wiping is just a function of that. Anyhow, accompanying the main unit is a small (and very crude!) demo designed for a 7″ tablet (a Nook HD in my case): TAndroidPreferencesIniFile demo The demo works best if you write a series of values first, given the third edit box doubles as both the entry field for values to write and default values to read.

Update (17/9/13): originally the unit was called CCR.Android.PrefsIniFile.pas; it’s now CCR.PrefsIniFile.Android.pas for consistency with the naming pattern used by my TClipboard units. I’ve also now added a ‘mobile’ version of the demo which uses TAndroidPreferencesIniFile when targeting Android, TApplePreferencesIniFile when targeting iOS, and TRegistryIniFile when targeting Windows (Delphi provides the Windows option in a mobile project to allow ‘quick and dirty’ debugging).

Update (25/6/15): current version now at http://github.com/chrisrolliston/CCR.PrefsIniFile

FMX TClipboard now supports iOS

I’ve just checked in a revision of my open source FMX TClipboard implementation that has an iOS backend. This supports the current rather than the FPC-based version of ‘Delphi for iOS’, however the Windows and OS X backends still compile with XE2 and above.

In essence, the new code wraps the native iOS clipboard API (UIPasteboard) and presents it in a fashion that closely follows the VCL clipboard interface, just like the existing Windows and OS X support did the same for desktop platforms. For some reason Apple in their wisdom decided to make the iOS clipboard API similar yet randomly different to the OS X one, so even though there’s not masses of code, it was a bit fiddly to implement. Anyhow, I’ve also knocked out a little demo similar to the previous desktop one:

TiOSClipboard demo

Using (say) Photos, you can copy an image to the clipboard and paste it into the demo. Conversely, you can from the demo itself copy either just the text entered, just the image, both the text and the image as two representations of the same clipboard item, or both the text and image as a custom clipboard format. The ‘List Formats on Clipboard’ button, as its name implies, then lists the formats currently on the clipboard. This is what I get after copying an image from Photos on the iOS simulator:

Format List

Technically, the iOS clipboard, like the OS X one, can have multiple items, each with multiple representations. Since the multiple-item concept doesn’t exist on Windows (indeed, it didn’t exist on OS X originally either), my class is only concerned with the first item, which is what most applications only bother with anyhow.

If you want the code, the SVN URL for it and a few other pieces is the following:

http://delphi-foundations.googlecode.com/svn/trunk/FMX%20Utilities/

The core files are now CCR.FMXClipboard.pas, CCR.FMXClipboard.Apple.pasCCR.FMXClipboard.iOS.pas, CCR.FMXClipboard.Mac.pas and CCR.FMXClipboard.Win.pas, and together they stand alone.