Fixing a FireMonkey TCheckBox/TAction bug

Try this:

  • Create a new FireMonkey HD application, before adding a check box and an action list to the form.
  • Double click the action list and add an action to it; set the action’s AutoCheck property to True.
  • Assign the action just created to the check box’s Action property.
  • Double click the form to create a handler for its OnCreate event. In the handler, set the action’s DisableIfNoHandler property to False:
procedure TForm1.FormCreate(Sender: TObject);
begin
  Action1.DisableIfNoHandler := False;
end;
  • Run the application; once showing, click the check box. Expected: its checked state is toggled. Actual: nothing happens
  • In fact, toggling the check box does work, however you need to double click the control for this to work. This is patently a bug caused by the IsChecked property setter getting called twice in the course of a single mouse click

To fix the problem, you can at a pinch create a suitable interposer class. Doing that won’t be very ‘clean’ however because the IsChecked property setter isn’t virtual, so do this instead:

  • Take a copy of FMX.Controls.pas, add it to your project, and open it up.
  • Find your way to the implementation of TCheckBox.MouseUp.
  • Ignoring the reams of ‘Live’Bindings support code, add a check for IsCheckedStored in front of IsChecked being toggled:
if IsCheckedStored then IsChecked := not IsChecked;

Rebuild the application, and the bug should be fixed.

FireMonkey’s BoundsRect bug, and working around it

Just a quick one, but the BoundsRect property getter in the FireMonkey TControl is incorrectly implemented, returning (0, 0, Width, Height) rather than (Position.X, Position.Y, Position.X + Width, Position.Y + Height). At first you might be tempted to say the property just has different semantics to the VCL version, however the implementation of the property setter gives the lie to that. Steps:

  1. Create a new FireMonkey HD application.
  2. Add a button to the form.
  3. Handle the button’s OnClick event like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Button1.BoundsRect := Button1.BoundsRect;
end;

Expected: nothing to happen. Actual: the button moves to the top-left corner of the form.

To work around this bug, either don’t read BoundsRect in the first place (it is after all just a small utility property), or put the following class helper in scope:

type
  TControlHelper = class helper for TControl
  strict private
    function GetBoundsRect: TRectF;
    procedure SetBoundsRect(const Value: TRectF);
  public
    property BoundsRect: TRectF read GetBoundsRect
      write SetBoundsRect;
  end;

//...

function TControlHelper.GetBoundsRect: TRectF;
begin
  Result.Left := Position.X;
  Result.Top := Position.Y;
  Result.Right := Result.Left + Width;
  Result.Bottom := Result.Top + Height;
end;

procedure TControlHelper.SetBoundsRect(const Value: TRectF);
begin
  SetBounds(Value.Left, Value.Top, Value.Width, Value.Height);
end;

Update 20/12/13: as pointed out in the comments, while GetBoundsRect remains as miscoded as ever in XE5, there is also a ParentedRect read-only property that does what the BoundsRect property getter should do. What a mess!

Fixing a TForm.BorderIcons bug in FMX/OS X

If you try removing biMaximize from a FireMonkey form’s BorderIcons property, you’ll find the ‘maximise’ (zoom) button on OS X (i.e., the green ‘traffic light’) resolutely staying both visible and enabled. The reason for the bug is the following code in FMX.Platform.Mac.pas, and it exists in both XE2 and XE3:

        if TBorderIcon.biMaximize in AForm.BorderIcons then
          Style := Style or NSWindowZoomButton;              

The problem here is that NSWindowZoomButton is not a valid member of a Cocoa window style ‘set’, a fact the compiler wasn’t able to pick up given Objective-C does not have strongly typed sets like Pascal. Further, you cannot in fact hide or disable the zoom button via the window style – instead, you get a reference to the actual button and hide or disable it ‘directly’.

As Apple’s UI guidelines say to disable an unwanted zoom button rather than hide it, that’s what we’ll do for our fix. So, having taken a copy of FMX.Platform.Mac.pas (or if you’ve been following this blog before, with your latest version of it open), head to TCocoaPlatform.CreateWindow. Next, comment out the lines quoted above (i.e. ‘if TBorderIcon…’). Then, immediately after the NSWin.initWithContentRect call, add the following:

    if (Style and not NSResizableWindowMask <> 0) and
      not (TBorderIcon.biMaximize in AForm.BorderIcons) then
    begin
      NSWin.standardWindowButton(NSWindowZoomButton).setEnabled(False);
      NSWin.standardWindowButton(NSWindowZoomButton).setTarget(nil);
    end;

Setting the ‘target’ to nil is necessary, otherwise the Enabled property will get automatically re-enabled – roughly, you can think of the button as having had an action assigned to it when created, and setting the Target to nil as like setting the Action property to nil again.

FMX tip: ‘labels’ with selectable text

Say you are designing a form in FireMonkey, and wish to use a read-only edit box. No problem: stick a TEdit on the form and set its ReadOnly property to False. However, doing this doesn’t give any visual indication the edit box’s contents can’t be changed. Moreover, what if you’re using a TEdit rather than a TLabel simply to allow users to copy the text, if they really want to, in which case the edit box should completely blend into the form background and not even have a border? A common example of this is the version number text shown in an application’s ‘about’ box, for example the Delphi IDE’s or Firefox’s.

In the VCL removing a TEdit’s background is no problem – set its ParentColor property to True, and its BorderStyle property to bsNone. Neither the Color, ParentColor not BorderStyle properties exist on the FMX TEdit however, since all these attributes are controlled by its style.

So, one’s first thought might be to create a custom style for the edit box. To do that, right click on the TEdit in the form designer, then choose Edit Custom Style… from the popup menu. Once the style editor is showing, head to the structure view, open up the control hierarchy, select the item labelled ‘background’, then click the little ‘delete’ button (top left). Choose ‘Apply and Close’, and both the edit box’s white background and its thin border should be gone. Great!

Custom TEdit style with default main style active

Alas, but there’s a problem – if you change the global style, the edit box’s custom style won’t be updated accordingly. E.g., here’s what happens when the ‘dark’ style is loaded:

Custom TEdit style with different main style active

The problem here is that custom styles set up at design time involve merely ‘copy and paste “inheritance”‘ from whatever style is used by the IDE at the time. Change the main style for an application later, and any custom style won’t have its underpinings updated accordingly.

While this inherent limitation of the FMX styling system is bad enough in the present scenario, it is even worse for things like customising a list box item’s style, since the default platform styles differ (as you might expect) on details like highlight colours – just try running XE3’s CustomListBox demo on OS X to see this in action. Another pain point is the XE3 platform styles relying on their own bitmaps, one per platform – since no care was taken for each control to refer to the same part of the main bitmap when running on Windows and OS X, customising a control’s style at design time can lead to half of it disappearing when the program is run on a Mac!

What we need to do, then, is not use a custom style, but correct the default style for a read-only edit box at runtime, specifically by manipulating things in a handler for the control’s OnApplyStyleLookup event. Learning what to modify exactly means delving into the source and the stock style files, preferably more than one in the case of the latter. To take the TEdit case, for example, while all the standard styles define its background by a control with a style element name of ‘background’, the control type depends upon the style. In the case of the ‘platform’ styles it is a TSubImage, which references part of one big platform-specific bitmap. In the case of the additional, vector-only styles however (like ‘dark’), a TRectangle is used. Given that, removing the background must be done in a way that is neutral between both primitive control types (i.e., TSubImage and TRectange). In fact, it’s a good idea to make the code as generic as possible, since it’s perfectly possible Embarcadero will change the internals once more in XE4 or whatever.

Anyhow, to cut to the chase, this is what I’ve come up with –

procedure TForm2.LabelEditApplyStyleLookup(Sender: TObject);
var
  Obj: TFmxObject;
begin
  Obj := (Sender as TCustomEdit).FindStyleResource('background');
  if Obj is TControl then TControl(Obj).Opacity := 0;
end;

Why make the background control transparent rather than just delete it? Ultimately, because I’m not keen on just randomly deleting internal objects. Further, talk of automatic reference counting (ARC) being introduced (see here) makes me nervous simple Free calls on FMX objects (*) will even be possible in the future given the inherent conflict between reference counting and the TComponent ownership pattern that FMX currently employs.

(*) And no, I’m not predicting ARC will be forced (or even introduced at all) for Win32/Win64 projects. I’m purely talking about FMX, on mobile and possibly desktop too given much of the framework will presumably cross compile between mobile and desktop platforms.

FMX tip: buttons with images that still have nicely-aligned captions

Unlike its VCL equivalent, the FireMonkey TSpeedButton has no Glyph property; similarly, TButton doesn’t have the image properties added to the VCL version in D2009, and in FMX, there’s no TBitBtn class to boot. The reason for this is that in principle, there is no need for class-specific image properties:

  1. Add a TImage to the design surface.
  2. Using the structure view (top left of the IDE), reparent the image to the desired button by dragging and dropping with the mouse. (It doesn’t matter whether the button is a TButton, which takes the keyboard focus, or a TSpeedButton which doesn’t.)
  3. Select the image and use the object inspector to set its HitTest property to False. This prevents it from receiving mouse clicks.

This approach works fine if the button doesn’t have a caption, but when it does, an issue arises concerning alignment: the text will remain centred in the button regardless of whether the image is aligned to a certain side, so that if the image is relatively large, text will appear partly on it, and partly on the normal background:

Poorly aligned button captions

If the button images are either left-aligned (as in this example) or right-aligned, you could manually fix up the alignment by prepending or appending spaces to the button’s Text property as appropriate, however this won’t work for top- or bottom-aligned images, and moreover, causes hassles if the button’s text comes from an assigned action. So, what would make for a more principled fix?

Well, internally, a button’s text is part of its style, which like the style for any other control is composed of more primitive FMX controls. The text, then, comes from a TText control, which is placed inside a layout control of some sort that also contains a TSubImage or TRectangle that defines the button background and border.

Given that, one way to fix the text alignment issue might be to reparent our TImage to the parent of the TText at runtime. If that sounds a bit hacky, that’s because it is, and worse, the changes made to control style handling in XE3 blogged about by Eugene Kryukov (EMBT) here make it precarious to boot. Happily however, there’s a better way: when the button’s style is loaded (or reloaded), adjust the internal TText’s Padding property so that it no longer overlaps the image.

To do this generically for a number of buttons on a form, select them all in the designer (they can be a mixture of TButton and TSpeedButton controls if you want), click to the Events tab of the Object Inspector, type ButtonApplyStyleLookup next to OnApplyStyleLookup, and press Enter. This will create a shared event handler for the buttons’ OnApplyStyleLookup event. In the code editor, add the following for its implementation:

procedure TForm1.ButtonApplyStyleLookup(Sender: TObject);
var
  Button: TCustomButton;
  Control: TControl;
  TextObj: TFmxObject;
begin
  Button := (Sender as TCustomButton);
  for Control in Button.Controls do
    if Control is TImage then
    begin
      TextObj := Button.FindStyleResource('text');
      if TextObj is TText then
        case Control.Align of
          TAlignLayout.alLeft:
            TText(TextObj).Padding.Left := Control.Width;
          TAlignLayout.alTop:
            TText(TextObj).Padding.Top := Control.Height;
          TAlignLayout.alRight:
            TText(TextObj).Padding.Right := Control.Width;
          TAlignLayout.alBottom:
            TText(TextObj).Padding.Bottom := Control.Height;
        end;
      Break;
    end;
end;

What we do here is cycle through the button’s controls looking for the TImage; when we find it, we then look for the TText style resource and adjust its Padding accordingly. Here’s the result:

Nicely aligned button captions

PS – if having read this far you’d still prefer a button class with a dedicated Bitmap property, check out Mike Sutton’s custom TSpeedButton descendant here.

Making the never-ending sorry tale of FMX menus a bit happier

For me, FireMonkey at present has three main showstopping limitations: an inability to draw text that is neither wonky (XP/Vista) nor overly ‘sharp’ (W7/W8) on Windows; a TMemo that isn’t fit for purpose on any platform; and a menu implementation that is poor on Windows, and hopelessly broken on OS X. Well, perhaps not quite ‘hopelessly’ – while the only proper solution would be to untangle the source and use native menus throughout, in the meantime, I have found some workarounds for specific issues: in particular, three concerning TMainMenu (= the FMX interface for the native menu bar) and two concerning TPopupMenu.

Problem: the Mac menu bar is owned by the application, but FMX thinks it should be owned by a form

In a traditional Mac application whose raison d’être is to view or edit individual files, it is possible for the application to be running yet with no windows open, in which case you just have the application’s menu bar showing when you Cmd+Tab to it. Alas, but TMainMenu cannot fathom such a thing, since it thinks it must always be attached in spirit to a specific form even if it is not attached visually, as it would be on Windows. Put a TMainMenu on a data module, then, and nothing happens.

To get around this, define an interposer class for TMainMenu that firstly overrides RecreateOSMenu to do nothing, and secondly provides an explicit Activate method:

type
  TMainMenu = class(FMX.Menus.TMainMenu, IItemsContainer)
  protected
    procedure RecreateOSMenu; override;
  public
    procedure Activate;
  end;

//...

procedure TMainMenu.Activate;
begin
  IFMXMenuService(TPlatformServices.Current.GetPlatformService(
    IFMXMenuService)).CreateOSMenu(Application.MainForm, Self);
end;

procedure TMainMenu.RecreateOSMenu;
begin
  { do nothing - Activate method now controls this }
end;

You still need to provide a target form for Windows, but the CreateOSMenu code on OS X just ignores it (at least for now!). Anyhow, with the Activate method defined, you can now put a TMainMenu on an auto-created data module and (if you want that) only have the menu appear when running on OS X (since a menu bar will be there anyway).

Problem: when a menu item’s state is changed (e.g., its Enabled or Visible property is toggled), the FMX main menu code rebuilds the whole menu

This is just a complete WTF, but anyhow, sanity can be found in providing an alternative implementation of the IFMXMenuService interface, in harness with an interposer class for TMenuItem. For the details, see here.

Problem: if an item on a main menu has been assigned an action that controls its core properties (Enabled, Visible, Text, etc.) and the action doesn’t get updated independently, the action’s OnUpdate handler will never get called

To see this problem in action, do this:

1. Create a new FMX HD application, add a TEdit and TButton to the form, followed by a TActionList and a TMainMenu.

2. Add an action called actCopy to the action list, set its Text property to ‘Copy’, and handle its OnUpdate and OnExecute functions like this:

procedure TForm1.actCopyExecute(Sender: TObject);
begin
  (Focused.GetObject as TEdit).CopyToClipboard;
end;

procedure TForm1.actCopyUpdate(Sender: TObject);
var
  Obj: TFmxObject;
begin
  if Focused = nil then
    actCopy.Enabled := False
  else
  begin
    Obj := Focused.GetObject;
    actCopy.Enabled := (Obj is TCustomEdit) and
      (TCustomEdit(Obj).SelLength > 0);
  end;
end;

3. Add an top level item to the main menu, and a second level item under it which then gets assigned actCopy to its Action property. If you are using the TMainMenu interposer class discussed previously, add a call to MainMenu1.Activate in a handler for the form’s OnCreate event.

4. Run the application; notice that when the menu is pulled down, the Copy item is enabled regardless of whether the TEdit has the focus and has text selected.

Now, because I’m only interested in TMainMenu on OS X, the fix I’m about to suggest is Mac-specific. The principle should be similar on Windows though. Secondly, this fix presupposes the previous one, i.e. the alternative IFMXMenuService implementation and TMenuItem interposer class that doesn’t rebuild everything from scratch just because a single Enabled property has changed. If you cause the main menu to be completely recreated when you are handling one of its events, then surprise surprise, the OS doesn’t like it, and will in fact terminate the application there and then in the OS X case.

Anyhow, those caveats aside, take a copy of FMX.Platform.Mac.pas, add the copy to the project, and make the following edits to it:

  • Find the NewNSMenu method declaration, and change the parameter from Text: string to Source: TMenuItem.
  • Amend all calls to NewNSMenu accordingly (i.e., pass in the menu item itself, not the menu item’s text). There’s one call you won’t be able to adjust for, so comment it out and use this code instead:
//LNewMenu := NewNsMenu(FMXAppName);
LNewMenu := TNSMenu.Wrap(TNSMenu.Alloc.initWithTitle(NSSTR(FMXAppName)));
  • Where FMXOSMenuItem and TFMXOSMenuItem are defined, add the following interface and class on similar lines:
  FMXOSMenu = interface(NSMenu)
  ['{E0653081-A641-4220-B7D3-2F1CF87E071E}']
    procedure menuNeedsUpdate(menu: Pointer); cdecl;
  end;

  TFMXOSMenu = class(TOCLocal, NSMenuDelegate)
  strict private
    FSource: TMenuItem;
  public
    constructor Create(const ASource: TMenuItem);
    destructor Destroy; override;
    function GetObjectiveCClass: PTypeInfo; override;
    procedure menuNeedsUpdate(menu: Pointer); cdecl;
  end;

constructor TFMXOSMenu.Create(const ASource: TMenuItem);
begin
  inherited Create;
  FSource := ASource;
  UpdateObjectID(NSMenu(Super).initWithTitle(NSSTR(DelAmp(ASource.Text))));
  NSMenu(Super).setDelegate(Self);
end;

destructor TFMXOSMenu.Destroy;
begin
  NSMenuItem(Super).Release;
  inherited;
end;

function TFMXOSMenu.GetObjectiveCClass: PTypeInfo;
begin
  Result := TypeInfo(FMXOSMenu);
end;

procedure TFMXOSMenu.menuNeedsUpdate(menu: Pointer); cdecl;
var
  I: Integer;
  Item: TMenuItem;
begin
  try
    for I := FSource.ItemsCount - 1 downto 0 do
    begin
      Item := FSource.Items[I];
      if Item.Action <> nil then
        Item.Action.Update;
    end;
  except //an unhandled exception here will kill the program
    Application.HandleException(ExceptObject);
  end;
end;
  • Amend the implementation of NewNSMenu itself to look like this:
function TPlatformCocoa.NewNSMenu(const Source: TMenuItem): NSMenu;
var
  AutoReleasePool: NSAutoreleasePool;
begin
  AutoReleasePool := TNSAutoreleasePool.Create;
  try
    Result := NSMenu(TFMXOSMenu.Create(Source).Super);
    FNSHandles.Add(AllocHandle(Result));
  finally
    AutoReleasePool.release;
  end;
end;
  • After ensuring OS X is the target platform, re-run the application: the ‘Copy’ menu item should now disable itself as appropriate.

The background to this code is that every top level menu item will be a NSMenu, and likewise every originating item in a multi-level menu setup, e.g. the ‘Recent’ item under ‘File’ that pops out a list of the recently-opened files. Any NSMenu can then be assigned a delegate object to handle certain events, one of which – menuNeedsUpdate – is called to allow you to update sub-items’ state as necessary. In our case, we have the delegate be NSMenu (or rather, a subclass of NSMenu) itself. In this capacity, the class then handles the menuNeedsUpdate message by asking sub-items’ actions to update themselves.

Now, there is a slight flaw in this fix, and in particular the TFMXOSMenu class, since in short, its destructor will never get called. This is an occupational hazzard of the fact that when you define an Objective-C class (as we do here) using the Delphi to Objective-C bridge, the reference counting of the core Objective-C/Cocoa part of instantiated objects and the reference counting of the Delphi part are completely separate from one another. Alas, but the TFMXOSMenuItem class of the original unit suffers from this same flaw. However, since the menu bar isn’t something you should be recreating at will – rather, it should be created at start up and left there until the application terminates – this isn’t a big deal in practice given OS X, like Windows, will reclaim all allocated resources when the process terminates anyhow.

Problem: TPopupMenu in FMX has no OnPopup event

With the VCL TPopupMenu, I frequently make use of its OnPopup event. Unfortunately no such event exists in the FMX equivalent. Nonetheless, an interposer class can fix that easily enough:

type
  TPopupMenu = class(FMX.Menus.TPopupMenu)
  strict private
    FOnPopup: TProc<TObject>; //or TNotifyEvent
  public
    procedure Popup(X: Single; Y: Single); override;
    property OnPopup: TProc<TObject> read FOnPopup write FOnPopup;
  end;

procedure TPopupMenu.Popup(X, Y: Single);
begin
  if Assigned(FOnPopup) then
    FOnPopup(Self);
  inherited;
end;

When the interposer class is in scope, you can now assign an OnPopup handler at runtime.

Problem: TPopupMenu forgets the order of hidden items

Say you have a popup menu whose items you conditionally hide (i.e., set their Visible properties to False) at various times. In such a situation, after every time the menu is shown, it will have pushed the hidden items to the top of the list, so that when you unhide them later, they will appear in a different position to what their were originally.

The underlying cause is that when a TPopupMenu is shown, it reassigns visible items’ Parent property to the control that will actually be ‘poping up’, before reassinging the properties back to itself once the popup closes. My preferred workaround is once more an interposer class, adding code to explicitly maintain the items’ order. Continuing with the previous example, the overridden Popup method should be rewritten to look something like this:

//TPopupMenu declaration as before...

type
  THiddenRec = record
    Item: TControl;
    Index: Integer;
  end;

procedure TPopupMenu.Popup(X, Y: Single);
var
  HiddenList: TList<THiddenRec>;
  Rec: THiddenRec;
  I: Integer;
begin
  if Assigned(FOnPopup) then
    FOnPopup(Self);
  //fix stock code not maintaining order of hidden items
  HiddenList := nil;
  try
    for I := ItemsCount - 1 downto 0 do
    begin
      Rec.Item := Items[I];
      if not Rec.Item.Visible then
      begin
        Rec.Index := Rec.Item.Index;
        if HiddenList = nil then
          HiddenList := TList<THiddenRec>.Create;
        HiddenList.Add(Rec);
        Rec.Item.Parent := nil;
      end;
    end;
    inherited;
    if HiddenList <> nil then
      for I := HiddenList.Count - 1 downto 0 do
      begin
        Rec := HiddenList[I];
        InsertObject(Rec.Index, Rec.Item);
      end;
  finally
    HiddenList.Free;
  end;
end;

That’s it for now. If nothing else, my patience is getting rather frayed…

FMX/OS X tip: enabling compiler optimisations to avoid weird startup crashes

I wasn’t able to trace the precise change that did it, but at a certain point I had the peculiar problem of an OS X debug build crashing at startup, and hanging the IDE if I attempted to run it in the debugger. Switching to the ‘release’ build configuration allowed it to run, but when you’re working with a framework that is so, er, work-in-progress as FireMonkey, not being able to run an application in the debugger for a certain target platform is rather debilitating.

Seeking to investigate the problem, I created a copy of the project and progressively removed units until it ran properly again. This led to things getting even weirder though, since use of generic types in unit initialization clauses or class constructors was seemingly the straw that broke the camel’s back! However, I finally hit upon a solution, which was to enable compiler optimisations (the default is ‘off’ in the stock debug configurations). To do this, I went to Project|Options, chose the second node down (Delphi Compiler -> Compiling), ensured ‘Debug configuration – OS X platform’ was selected in the combo box at the top, then ticked the ‘Optimization’ box and finally clicked OK.

Notwithstanding this issue, overall I’d say remote debugging in XE2 and XE3 from a Windows guest to an OS X host is pretty smooth (if a bit bumpy once exceptions get raised), so don’t let this put you off…