InfoPower grid for FireMonkey – free with XE5

In case anyone interested in FMX isn’t aware, Woll2Woll’s InfoPower grid for FireMonkey can be got free with XE5. OK, no source, but it’s much, much better than the standard offering:

InfoPower

If you want to see the Android targeting in action, Woll2Woll have a demo on Google Play (https://play.google.com/store/apps/details?id=com.woll2woll.IP2SearchGridDemo&hl=en).

That said, one small issue I’ve found is that the setup file available on ‘My Registered Downloads’ at Embarcadero’s site is a bit outdated – on opening one of the iOS demos and building, I got a compiler error:

[DCC Fatal Error] SearchGrid.pas(10): F2051 Unit fmx.wwlayouts was compiled with a different version of FMX.Effects.TEffect

Presumably this is due to update 2 breaking DCU compatibility for iOS targets (this issue is mentioned in the update 2 readme files). To fix, I downloaded the latest setup file from Woll2Woll’s website and installed over the top of the original installation – see the top link here:

http://www.woll2woll.com/download-infopower-release-versions.html

Having done that, the iOS demos now compiled fine.

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.

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…

Dragging an FMX list box item to a grid without superfluous highlighting

This a somewhat specific problem, but anyhow, I have an FireMonkey application I’m writing in which I was wanting to be able to drag a list box item to a grid. Now, the FMX TListBox has an AutoDrag property to allow its items to be dragged, and FMX controls – like VCL ones – have OnDragOver and OnDragDrop events in which the former allows you to say whether the control will accept a dragged object and the latter allows the control to actually accept it. While a TListBox’s AutoDrag property is geared to allowing the user to reorder its items internally, setting it to True allows the user to drag an item outside the list box as well.

All in all then, the core functionality I required was implemented by the framework. However, by default, the behaviour wasn’t quite what I wanted:

  • While making it no effort to have a list box item draggable outside its parent was great, I didn’t want the accompanying reordering visuals when the dragged item was still within the list box bounds.
  • Most FMX controls visually indicate that they will accept an object being dragged over them regardless of any OnDragOver handler being set. So, if the form has a list box, a grid and a panel, and a list box item is being dragged, all three controls, together with the remaining list box items, will appear to accept the item as the item is dragged over it. (In the list box’s case, the entire list box is highlighted if you aren’t hovering over a specific item.)
  • In the case of a grid, each column header will indicate acceptance individually as well. Worse, they also support dragging and dropping between themselves to reorder and dragging to somewhere outside the grid with no obvious way to turn either ability off! Speaking for myself, this means I find it pretty easy to reorder the columns of a FMX grid without intending to. Moreover, the fact you can drag a grid header outside the grid is just confusing:

Panel drag highlight

In looking to fix these issues, the first thing I discovered was that the annoying visual acceptance effect is controlled by the EnableDragHighlight property, as introduced with public visibility (and a default setting of True!) in TControl, and subsequently published by most descendants. This property has zero connection to the OnDragOver event, which means that regardless of what you do in an OnDragOver handler, or indeed whether you have a handler for that event at all, by default, a control will visually indicate it accepts anything and everything being dragged over it. Nonetheless, if you have a number of controls that will never accept a dragged object, the easy fix is just to select them all at design time and toggle their EnableDragHighlight property to False. In my case that meant selecting everything on the form, deselecting the popup menus (which don’t have an EnableDragHighlight property), toggling the property in the Object Inspector, then individually putting the property back on a case-by-case basis.

This was also the fix for removing the reordering visuals from the list box, since TListBoxItem is one of the TControl descendants that publish EnableDragHighlight. In principle, you should still need to remove the actual reordering if an internal drag and drop happens accidentally. However, due to an oversight in the FMX source, the actual reordering doesn’t happen – the items are repositioned in the internal list of child objects, but their positions on screen are not switched similarly. Assuming that bug will one day be fixed though, the way to prevent any reordering is to handle the list box’s OnDragChange event and set the Allow parameter to False:

procedure TForm1.ListBox1DragChange(SourceItem,
  DestItem: TListBoxItem; var Allow: Boolean);
begin
  Allow := False;
end;

So, that left the grid headers… oh, and the listbox itself, since while setting EnableDragHighlight to False in the Object Inspector worked for individual list box items, it stubbornly wasn’t for their parent control:

List box drag highlight

Looking at the source, the reason stemmed from the AutoDrag property setter forceably setting EnableDragHighlight to True. Why? Who knows, but anyhow, where EnableDragHighlight is published by an ancestor class, TScrollBox, AllowDrag is published by TCustomListBox. Ergo, the persisted value of AllowDrag will be read back in after the persisted value of EnableDragHighlight, causing the former to override the latter. The fix is therefore to set the list box’s EnableDragHighlight property to False at runtime in (say) the form’s OnCreate handler:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ListBox1.EnableDragHighlight := False;
end;

This just left getting rid of the grid column headers’ default draggability. Since a FMX grid’s header is defined by its style, the place to tweak the settings of it is in a handler for the grid’s OnApplyStyleLookup event. In terms of what needed to be done there, initially I thought of EnableDragHighlight again, but that wouldn’t solve the problem of the header items being themselves draggable. I then realised that the columns themselves were child controls of the grid just as much as the header, and they didn’t have independent draggability (so to speak). The reason for that, it became clear, was because their HitTest properties are set to False by the grid. So, the fix for the header was just to set both the header and its own child control’s HitTest properties to False likewise:

procedure TForm1.Grid1ApplyStyleLookup(Sender: TObject);
var
  StyleObj: TFmxObject;
  ChildObj: TControl;
begin
  StyleObj := (Sender as TCustomGrid).FindStyleResource('header');
  if StyleObj is THeader then
  begin
    THeader(StyleObj).HitTest := False;
    for ChildObj in THeader(StyleObj).Controls do
      ChildObj.HitTest := False;
  end;
end;

This of course means the user cannot click on a header item. If you want to have that, assign the header items’ OnClick handlers as desired, set their DragMode properties to TDragMode.dmManual, and their EnableDragHighlight properties to False:

procedure TForm1.Grid1ApplyStyleLookup(Sender: TObject);
var
  StyleObj: TFmxObject;
  ChildObj: TControl;
begin
  StyleObj := (Sender as TCustomGrid).FindStyleResource('header');
  if StyleObj is THeader then
  begin
    THeader(StyleObj).HitTest := False;
    for ChildObj in THeader(StyleObj).Controls do
    begin
      ChildObj.DragMode := TDragMode.dmManual;
      ChildObj.EnableDragHighlight := False;
      if ChildObj is THeaderItem then
        THeaderItem(ChildObj).OnClick := MyHeaderItemClick;
    end;
  end;
end;

(As an aside, you will frequently find the FMX source doing an Assigned check before using the ‘is’ operator – if the above code were written by an FMX author or maintainer, you would therefore expect to see ‘if Assigned(StyleObj) and (StyleObj is THeader) then’ rather than just ‘if StyleObj is THeader then’. This is completely unnecessary however, since the ‘is’ operator checks for nil itself.)

Retrieving the application’s version string

If you need to retrieve at runtime your application’s version number, you’re targeting Windows, and you don’t mind just having the major and minor numbers, then SysUtils has a handy GetFileVersion function for the task:

function GetAppVersionStr: string;
var
  Rec: LongRec;
begin
  Rec := LongRec(GetFileVersion(ParamStr(0)));
  Result := Format('%d.%d', [Rec.Hi, Rec.Lo])
end;

If you want the ‘release’ and ‘build’ numbers as well, you’ll need to drop down to the Windows API:

function GetAppVersionStr: string;
var
  Exe: string;
  Size, Handle: DWORD;
  Buffer: TBytes;
  FixedPtr: PVSFixedFileInfo;
begin
  Exe := ParamStr(0);
  Size := GetFileVersionInfoSize(PChar(Exe), Handle);
  if Size = 0 then
    RaiseLastOSError;
  SetLength(Buffer, Size);
  if not GetFileVersionInfo(PChar(Exe), Handle, Size, Buffer) then
    RaiseLastOSError;
  if not VerQueryValue(Buffer, '\', Pointer(FixedPtr), Size) then
    RaiseLastOSError;
  Result := Format('%d.%d.%d.%d',
    [LongRec(FixedPtr.dwFileVersionMS).Hi,  //major
     LongRec(FixedPtr.dwFileVersionMS).Lo,  //minor
     LongRec(FixedPtr.dwFileVersionLS).Hi,  //release
     LongRec(FixedPtr.dwFileVersionLS).Lo]) //build
end;

If targeting OS X then GetFileVersion isn’t available, so you need to drop down to the OS API again. This time the code is much simpler however:

uses Macapi.CoreFoundation;

function GetAppVersionStr: string;
var
  CFStr: CFStringRef;
  Range: CFRange;
begin
  CFStr := CFBundleGetValueForInfoDictionaryKey(
    CFBundleGetMainBundle, kCFBundleVersionKey);
  Range.location := 0;
  Range.length := CFStringGetLength(CFStr);
  SetLength(Result, Range.length);
  CFStringGetCharacters(CFStr, Range, PChar(Result));
end;

This uses Core Foundation; you could use Cocoa instead, but when an equivalent ‘Core’ API is available, it’s usually the better choice in Delphi given it can be called directly, and with code almost identical to the Objective-C samples available online and elsewhere.

Quick FMX tip – TBitmap.CopyFromBitmap is not synonymous with TBitmap.Assign

Quick FireMonkey tip – if you are calling the CopyFromBitmap method of TBitmap and wondering why it isn’t working (cf. the first issue reported here), it’s probably because you are attempting to write to an empty bitmap. In other words, like the CopyRect method in the VCL, CopyFromBitmap does not resize the destination bitmap, so the latter needs to be big enough beforehand if you want all the source pixels to be copied over. Nonetheless, if you want to resize the destination bitmap as well, just call Assign rather than CopyFromBitmap:

DestImage.Bitmap.Assign(SourceBitmap);