FMX tip – default style lookup names

This one was driving me up the wall – I’m working on (or rather trying to work on) a FireMonkey application, one form in which loads a tree view dynamically. Since each node concerns a certain sort of data object, I wrote a small custom descendant of TTreeViewItem to add a property of the relevant type and a couple of helper methods. So far so good. After that I did a bit of refactoring, separating out the descendant into a custom base class and two child classes, and adding bits and bobs. Following this I reran the program… and found the tree view text had disappeared. Argh!!! Progressively commenting out the new code got me nowhere, until tracing back my step of splitting out the custom descendant into three got me the answer – the now-grandchildren of TTreeViewItem were style-less!

If you check out the FMX source, you’ll see that the default style lookup for an object is its class name, minus the leading ‘T’ and with a ‘style’ prefix. This can be customised if you wish – in the form designer, it’s the StyleLookup property that gets set when you choose ‘Custom Style’ from the designer’s popup menu. If you customise it with the name of a style element that is not part of the active style (e.g., you used the ‘Custom Style’ command in the designer, only to delete the generated style book afterwards), then the default lookup based on the class name is used; and if that doesn’t name a valid style element, then the default style lookup for the parent class is used. If that still results in failure, then the control is just left style-less, and therefore empty at runtime unless the appearance is hardcoded (which is bad form for a FMX control). In my view, if you’re going to start walking up the class hierarchy for a valid style lookup, then you might as well continue to walk until you find one. In fact, more strongly, it surely breaks basic OOP principles for the act of merely splitting out a base class from a class to have the effect of altering (and in this case, breaking) its behaviour.

Regardless, my next step was to assign the StyleLookup property to ‘treeviewitemstyle’ in my custom base class’ constructor. This fixed the original problem of text not appearing… but caused a new issue of check boxes showing even though the tree view’s ShowCheckboxes property remained False! Tracing into the code, I found assigning the StyleLookup property causes a control to actually load its style right there and then; in my custom class’ case, this meant the style was being loaded before the control’s parentage had been established, which was undoubtedly too early (while the default value of the ShowCheckboxes property is False, the default visibility of the check box element of a tree view item style is True). Nonetheless, the fix was easy: assign not the StyleLookup property, but the FStyleLookup protected field:

type
  TMyNodeBase = class(TTreeViewItem)
  public
    constructor Create(const AOwner: TComponent; const AData: TDataObject); reintroduce;
  end;

  TMyRedNode = class(TMyNodeBase)
  //...
  end;

  TMyBlueNode = class(TMyNodeBase)
  //...
  end;

constructor TMyNodeBase.Create(const AOwner: TComponent; const AData: TDataObject);
begin
  inherited Create(AOwner);
  FStyleLookup := 'treeviewitemstyle'; //prevent descendants being style-less
  FData := AData;
end;
Advertisements

Potential XE3 gotcha – dodgy old code vs. new TStream overloads

In XE3, TStream has acquired a number of new helper methods for Read and Write. Mostly these take the form of strongly-typed versions of Read, Write, ReadBuffer and WriteBuffer, called ReadData, WriteData, ReadBufferData and WriteBufferData respectively. These relieve you of the need to use SizeOf:

var
  Flag: Boolean;
  MagicNum: Integer;
begin
  Stream.ReadBufferData(Flag);
  if Flag then Stream.ReadBufferData(MagicNum);

I think adding these methods was a small but good idea, and a much better one than the TBinaryReader/TBinaryWriter classes that were pointlessly added in XE (cf. IOUtils, TStringBuilder, IEnumerable… ever get the feeling someone on the development team just never got over Big Bad EMBT Management killing off Delphi.NET?). It’s annoying whoever wrote the code hasn’t heard of static arrays, but the interface is fine.

However, there are also new overloads for Read, Write, ReadBuffer and WriteBuffer themselves. These take a TBytes parameter rather than an untyped buffer, and are presumably done for compatibility with the already-announced new compiler for targeting iOS and Android:

  function Read(var Buffer: TBytes; Count: Longint): Longint; overload;
  function Read(var Buffer: TBytes; Offset, Count: Longint): Longint; overload;
  function Write(const Buffer: TBytes; Count: Longint): Longint; overload;
  function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; overload;
  procedure ReadBuffer(var Buffer: TBytes; Count: Longint); overload;
  procedure ReadBuffer(var Buffer: TBytes; Offset, Count: Longint); overload;
  procedure WriteBuffer(Buffer: TBytes; Count: Longint); overload;
  procedure WriteBuffer(Buffer: TBytes; Offset, Count: Longint); overload;

As shown by this QC report, the new overloads (and in particular, the ones without an additional Offset parameter) will alas be problematic though if you have old code that writes pointers on a stream on the understanding the addresses should be written.

Given pointer addresses are transient, this is in principle an odd thing to do. For myself, failing to dereference a pointer when calling Read- or WriteBuffer has caused infuriating crashes on more than one occasion! However, if you needed a list of integers in the past and lazily used a raw classic TList for the task, casting between Pointer and Integer as necessary, the code will need to be rewritten. The following, for example, will not work in XE3:

uses
  System.SysUtils, System.Classes;

var
  I: Integer;
  IntList: TList;
  Stream: TMemoryStream;
begin
  Stream := nil;
  IntList := TList.Create;
  try
    IntList.Add(Pointer(11));
    IntList.Add(Pointer(22));
    IntList.Add(Pointer(33));
    Stream := TMemoryStream.Create;
    //save list to the stream
    for I := 0 to IntList.Count - 1 do
      Stream.WriteBuffer(IntList.List[I], SizeOf(Integer));
    //clear the list before adding the items back from the stream
    IntList.Clear;
    Stream.Position := 0;
    while Stream.Read(I, SizeOf(Integer)) = SizeOf(Integer) do
      IntList.Add(Pointer(I));
    //show 'em
    for I := 0 to IntList.Count - 1 do
      WriteLn(Integer(IntList[I]));
  finally
    IntList.Free;
    Stream.Free;
  end;
end.

The problem here is that the when a value typed to Pointer is passed to an overloaded method that takes either an untyped paramater or a dynamic array, the dynamic array will be picked. This then causes an access violation, since the pointer being passed here isn’t actually a dynamic array of Byte – it’s an integer hard cast to a Pointer.

Now, perhaps there shouldn’t be type assignability here (for myself, I’d prefer only being able to assign a typed pointer of the appropriate sub-type, e.g. PByte to TBytes, PString to dynamic array of string and so on), however this has always been the case IIRC ever since dynamic arrays were added in D4. Morevoer, the proper way to do things before generics (which in practical terms meant until the generic TList actually worked properly) would have been to encapsulate TList, or at the very least hide the Pointer access members with Integer ones, a la Contnrs.pas’ TObjectList.

Because of these two points I think the closing of the QC report correct, notwithstanding the fact the narrative is wacko (‘test case error’? Er, no. XE3 has ‘fixed’ earlier behaviour? Er, no again – both old and new behaviours work correctly, and both stem from identical compiler semantics, they just conflict in a particular sort of case). Nonetheless, the overloads are still something to be wary of when recompiling old code.

Inspecting the default ‘platform’ FMX styles in XE3 (if you’re interested in that sort of thing)

One of the more obvious changes in FireMonkey between the XE2 and XE3 releases is the (much) better default styling on Windows in particular. To my eyes this makes FMX controls in XE3 look like custom drawn VCL ones that try to look native (e.g. TSpeedButton), which for me at least is ‘good enough’ in principle, notwithstanding the odd tweak that still needs to be made in practice.

The main technical reason for this improvement is the use of bitmaps rather than an all-vector approach. Going by the limited help and marketing information, you may think this involves a new FMX style format (the XE2 one was in essence just the old DFM format), but this is not true – rather, the bitmaps used by the new platform styles are just hosted in a TImage control inside the style structure. In order to avoid unnecessary duplication and the inefficient use of lots of small bitmaps, a new TSubImage component has also been added to allow each platform style to have one big bitmap that different style elements can then reference different parts of. While TSubImage itself is not registered onto the Tool Palette (perhaps an oversight?), you can find it present and correct in FMX.Objects.pas, and when you do, you’ll find there’s very little to it.

What might lead you astray in thinking there is more to things than that is the fact XE2’s VCL style editor has been renamed the ‘Bitmap Style Editor’ in XE3. However, the extra functionality behind the name change is an FMX style export facility. While this will probably prove a valuable feature, the program still cannot open even the native FMX style files (*.style) it itself has created. The (very) early market for third party FMX style editors like Mike Sutton’s MonkeyStyler is therefore still very much open!

Inspecting the XE3 platform styles

Annoyingly enough, unlike in XE2, the platform styles for FMX in XE3 are not directly distributed with the product. I say annoyingly, since the ability to easily inspect the details of a style can be very useful. For example, having used a TTreeView control with a TLabel embedded right-aligned in each node for additional text, I quickly discovered that the OS X style uses a different font colour for selected tree view items that my additional labels weren’t picking up. Wanting to identify this colour at runtime, I initially tried to use the IDE’s style editor to locate what properties I needed to look up. However, this proved a fool’s errand – quite apart from the fact the native FMX style editor seems to be missing half its proper functionality, there wasn’t the OS X style file around to load into it in the first place!

Nonetheless, on browsing the FMX source folder I quickly came across the FMX.Platform.Win.rc and FMX.Platform.Mac.rc resource scripts. While the style files these reference are not shipped, the scripts clearly identify the resource names used (win7style and win8style for the Windows platform styles, and lionstyle and lion2xstyle for the OS X ones); and on closer inspection, the style resources themselves proved to be the raw *.style files, saved in the binary DFM format, and prepended with a 13 byte header. Putting all this together, I quickly wrote the following program to extract human-readable *.style files:

program Project1;

{$R *.res}

{$R 'C:\Program Files\Embarcadero\RAD Studio\10.0\lib\win32\release\FMX.Platform.Win.res'}
{$R 'C:\Program Files\Embarcadero\RAD Studio\10.0\lib\osx32\release\FMX.Platform.Mac.res'}

uses
  System.Types, System.SysUtils, System.Classes;

procedure ExtractStyle(const ResName, DestFileName: string);
var
  Input: TResourceStream;
  Output: TFileStream;
begin
  Output := nil;
  Input := TResourceStream.Create(HInstance, ResName, RT_RCDATA);
  try
    Input.Seek(13, soCurrent);
    Output := TFileStream.Create(DestFileName, fmCreate);
    ObjectBinaryToText(Input, Output);
  finally
    Input.Free;
    Output.Free;
  end;
end;

begin
  ExtractStyle('win7style', 'C:\Users\CCR\Documents\Win7.style');
  ExtractStyle('win8style', 'C:\Users\CCR\Documents\Win8.style');
  ExtractStyle('lionstyle', 'C:\Users\CCR\Documents\Lion.style');
  ExtractStyle('lion2xstyle', 'C:\Users\CCR\Documents\Lion2x.style');
end.

Adjust the paths as appropriate, run the application, and you’re done.

And the prize for ugliest API goes to…

In FireMonkey/XE2, most platform-specific functionality was filtered through a Platform singleton object. In this, the FMX.Platform unit defined an abstract TPlatform base class, with FMX.Platform.Win and FMX.Platform.Mac providing (private) concrete descendants of it (TPlatformWin and TPlatformMac respectively) that did the actual work. In FireMonkey/XE3, in contrast, the Platform singleton has been removed in favour of a TPlatformServices object that you query different interfaces from, each interface providing a different sort of ‘platform service’ – for example, there’s a clipboard service (IFMXClipboardService), menu service (IFMXMenuService), and so on.

In practice, TPlatformWin and TPlatformMac still exist, and at present, they also implement all the interfaces you can query for when it comes to their particular platform. Nonetheless, in principle, the new setup allows providing an alternative implementation for any given IFMXxxxService interface. For example, if you’ve had enough of the awful stock Mac menu bar code, you can write your own implementation of the IFMXMenuService interface, unregister the default implementation, then register your replacement:

type
  TMacMenuServiceFix = class(TInterfacedObject, IFMXMenuService)
  strict private
    FDefaultImpl: IFMXMenuService;
    procedure DestroyMenuItem(const AItem: IItemsContainer);
    //...
  public
    constructor Create;
  end;

constructor TMacMenuServiceFix.Create;
begin
  inherited Create;
  FDefaultImpl := IFMXMenuService(
    TPlatformServices.Current.GetPlatformService(IFMXMenuService));
  TPlatformServices.Current.RemovePlatformService(IFMXMenuService);
  TPlatformServices.Current.AddPlatformService(IFMXMenuService, Self);
end;

In this example I store a reference to the stock implementation in order to delegate to it for methods that I don’t wish to customise.

All in all I think this change a positive one. Nonetheless, the API is – how can I put this – rather ugly! Check out how I retrieve the default implementation of IFMXMenuService above:

  FDefaultImpl := IFMXMenuService(
    TPlatformServices.Current.GetPlatformService(IFMXMenuService));

Notice how I have to type the word ‘service’ *four* times, the phrase ‘PlatformServices’ and ‘IFMXMenuService’ twice each, and the word ‘current’ once yet completely pointlessly – for some reason the person who wrote TPlatformServices doesn’t seem to realise there are things called class vars and class methods. I wonder if he’s related to whoever designed TTimeZone for the XE release…?

That said, wouldn’t it be better if we could just write this instead:

  FDefaultImpl := TPlatformService.Get<IFMXMenuService>;

Similarly, in cases where we are open to the possibility there is no default implementation, you have to write code like the following, annoying cast included:

var
  MenuService: IFMXMenuService;
begin
  if TPlatformServices.Current.SupportsPlatformService(
    IFMXMenuService, IInterface(MenuService) then

Wouldn’t that be better like this though?

var
  MenuService: IFMXMenuService;
begin
  if TPlatformService.Available<IFMXMenuService>(MenuService) then

I think so, and whadya know, it took about two minutes to write:

  TPlatformService = record
    class function Available<IntfType: IInterface>(
      out Service: IntfType): Boolean; static;
    class function Get<IntfType: IInterface>: IntfType; static;
  end;

uses System.TypInfo;

class function TPlatformService.Available<IntfType>(
  out Service: IntfType): Boolean;
var
  Guid: TGUID;
begin
  Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid;
  Result := TPlatformServices.Current.SupportsPlatformService(
    Guid, IInterface(Service));
end;

class function TPlatformService.Get<IntfType>: IntfType;
var
  Guid: TGUID;
begin
  Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid;
  Result := IntfType(TPlatformServices.Current.GetPlatformService(
    Guid));
end;

[Edit: originally I had ‘TCurrentPlaformService’ rather than ‘TPlatformService’, but as Eric Grange rightly says in the comments, why have the word ‘current’ at all? There’s also an argument for dropping the ‘T’ prefix, but I’ve kept that in to be consistent with the wider D2009+ RTL.]

Quick tip – assigning Command+Letter shortcuts to FMX menu items

It’s not obvious since the forms designer doesn’t support it, but the way to assign a Cmd+Letter shortcut to a menu item in FireMonkey/OS X application in XE2 is to do so at runtime. For example, say you have a ‘file open’ item called itmFileOpen, and want to assign it the shortcut Cmd+O; to do so, create an OnCreate event handler for the form by double clicking it, then add the following line:

itmFileOpen.Shortcut := scCommand or Ord('O');

At runtime, this will ensure the proper shortcut symbol appears next to the item (⌘O), as well as make the actual key combination work.

Managed types vs. GetMem

I’ve just seen the following question on StackOverflow:

I am getting an unexpected AV in the following code:

program Project65;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils;

type
  ITest = interface
  end;

  TTest = class(TInterfacedObject, ITest)
  end;

var
  p: ^ITest;

begin
  GetMem(p, SizeOf(ITest)); 
  p^ := TTest.Create; // AV here
  try
  finally
    p^ := nil;
    FreeMem(p);
  end;
end.

Now interestingly if I change the first line to

GetMem(p, 21);

than the AV is gone. (20 bytes or less fails). What is the explanation to this?

Simple: GetMem, as stated by the documentation, does not initialise the memory it allocates to zero. The chances are, therefore, that p^ is not nil when it comes to be assigned to the new instance of TTest; if that is the case however, then the low-level RTL will assume it points to a valid interface reference, and call _Release accordingly. As it in fact points to random bytes, an AV results. Importantly, the fact it does appear to be zero-initialised for the questioner if 21 not 4 bytes are requested is irrelevant: not ensuring memory is zero-initialised does not imply ensuring memory isn’t zero-initialised.

Moral of the story? If you mix and match relatively high level features (managed types) with relatively low level ones (dynamically allocating raw memory blocks), don’t be surprised if you need to perform bookkeeping for the former that is normally done for you.

Now in this particular case, using AllocMem instead of GetMem would be a quick fix. Nonetheless, I would personally advise not mixing and matching higher and lower level language features like this, unless there’s a good framework-y reason to do so. If you want to use pointers, just use pointers!

[Update: in between me writing this and it coming up on DelphiFeeds.com, two perfectly serviceable answers appeared on StackOverflow – check them out.]

Update 4 Hotfix 1 – a small tip

‘Hotfix 1’ for XE2 update 4 is out. If you don’t want to download a new ISO, you can download a 250MB ZIP instead, which is what I did.

For me, installation on a Vista 32 bit VM I have went fine, but on a physical Windows 7 64 bit box, there was the traditional 20 minutes of MSI ‘validating’ the installation by throttling a core (small mercies the MSI developers don’t appear to have heard of the CreateThread function I suppose!). I also got this in the middle of it:

I had this with a previous update too. The problem is that the installer deletes the original file, then forgets it has done so and throws an ‘insufficient privileges’ error when it finds nothing to overwrite! The fix is simple – create a dummy bds.exe before clicking Retry. You can do this by opening Notepad with administrator privileges (e.g., by calling Notepad up in the Start Menu, right clicking on its icon and selecting Run as Administrator), and saving a blank file called bds.exe in the RAD Studio bin directory.

[Edit: just reading the forums, it seems some people can get away with just clicking ‘retry’ without creating a dummy file. That may have worked for me too, I don’t know, but a previous update wouldn’t continue without a dummy file being created.]