Improved XE5 mobile previews part 2

Last time around I blogged about how to extract the default XE5 mobile styles into *.style files – in a nutshell, you need to create an app that extracts the desired style resource to a file, then open the file in the stylebook editor, delete the ‘Description’ resource, and resave. In that post I went on to suggest using the extracted files in TStyleBook components. There is however a better way – provide a custom FMX.MobilePreview unit instead (NB: for all about the standard mobile previews – introduced in XE5 update 2 – check out either Marco Cantù’s blog post or the documentation).

To do this, create a new unit and, modeling on the standard FMX.MobilePreview.pas, add the following code:

unit FMX.MobilePreview;
{
  Use actual Android style
}
interface

implementation

{$IFDEF MSWINDOWS}

{$IF CompilerVersion <> 26}
  {$MESSAGE Error 'Custom FMX.MobilePreview.pas expects XE5!'}
{$IFEND}

uses
  System.Types, System.SysUtils, FMX.Styles, FMX.Graphics, FMX.Platform;

var
  OldInitProc: TProcedure;

procedure InitializeStyle;
begin
  if Assigned(OldInitProc) then
    OldInitProc();
  //!!!change path to whereever you have put the resaved *.style file
  TStyleManager.SetStyle(TStyleManager.LoadFromFile('C:\Delphi\LibXE5\Android.style'));
end;

initialization
  OldInitProc := InitProc;
  InitProc := @InitializeStyle;

finalization
  TStyleManager.SetStyle(nil);
  TCanvasManager.UnInitialize;
{$ENDIF}
end.

If you save the unit as FMX.MobilePreview.pas, compile and ensure the DCUs are in the global search path, you can have the IDE pick it up rather than the standard FMX.MobilePreview.pas. Alternatively, save it under a different name and just amend your project files to use it rather than FMX.MobilePreview.

That said, if you do this, something still won’t quite be right – namely, the font:

Font not right

This is because in the current FireMonkey code, the default font is a property of the platform rather than the style. As such, the font used here is a regular Windows one rather than Roboto, the default font on Android. Fixing this discrepancy is easy however – back in the custom unit, first add the following class immediately after the uses clause:

type
  TFMXSystemFontService = class(TInterfacedObject, IFMXSystemFontService)
  public
    function GetDefaultFontFamilyName: string;
  end;

function TFMXSystemFontService.GetDefaultFontFamilyName: string;
begin
  Result := 'Roboto';
end;

Secondly, unregister the standard IFMXSystemFontService implementation before registering our own by adding the following lines at the top of the unit’s initialization block:

  TPlatformServices.Current.RemovePlatformService(IFMXSystemFontService);
  TPlatformServices.Current.AddPlatformService(IFMXSystemFontService, TFMXSystemFontService.Create);

In all, the code for unit should now look like this:

unit FMX.MobilePreview;
{
  Use actual Android style
}
interface

implementation

{$IFDEF MSWINDOWS}
{$IF CompilerVersion <> 26}
  {$MESSAGE Error 'Custom FMX.MobilePreview.pas expects XE5!'}
{$IFEND}
uses
  System.Types, System.SysUtils, FMX.Styles, FMX.Graphics, FMX.Platform;

type
  TFMXSystemFontService = class(TInterfacedObject, IFMXSystemFontService)
  public
    function GetDefaultFontFamilyName: string;
  end;

function TFMXSystemFontService.GetDefaultFontFamilyName: string;
begin
  Result := 'Roboto';
end;

var
  OldInitProc: TProcedure;

procedure InitializeStyle;
begin
  if Assigned(OldInitProc) then
    OldInitProc();
  TStyleManager.SetStyle(TStyleManager.LoadFromFile('C:\Users\CCR\Downloads\Android FMX (resaved, and minus description).style'));
end;

initialization
  TPlatformServices.Current.RemovePlatformService(IFMXSystemFontService);
  TPlatformServices.Current.AddPlatformService(IFMXSystemFontService, TFMXSystemFontService.Create);
  OldInitProc := InitProc;
  InitProc := @InitializeStyle;

finalization
  TStyleManager.SetStyle(nil);
  TCanvasManager.UnInitialize;
{$ENDIF}
end.

Run the mobile preview again, and the font is now how it should be:

Font fixed

Advertisements

Inspecting ‘platform’ styles redux

Back when XE3 was current I blogged about retrieving the default FMX styles for Windows and OS X, a process less transparent than in XE2 when the standard styles were distributed as *.style files. Roll onto mobile support in XE4 and XE5, and the default styles are if anything even more hidden. Nevertheless, it is possible to recover them and get human-readable *.style files:

1. Find the resource name; for Android this is currently ‘androidstyle’ (see FMX.Controls.Android.pas), for iOS one of ‘iphonestyle’, ‘ipadstyle’ or ‘iphonepadstyle_Modern’ (= iOS 7; see FMX.Controls.iOS.pas).

2. Create a blank new mobile project, and in the form’s OnCreate event, load and output the desired resource to a file. For example, for Android you might do this:

uses System.IOUtils;

procedure TForm1.FormCreate(Sender: TObject);
var
  FN: string;
  Stream: TResourceStream;
begin
  FN := TPath.Combine(TPath.GetHomePath, 'FMX default.style');
  Stream := TResourceStream.Create(HInstance, 'androidstyle', RT_RCDATA);
  Stream.SaveToFile(FN);
  ShowMessage('Saved to ' + FN);
end;

3. Run the app on a device.

4. Manually copy over the extracted file to your development PC or VM.

5. Back in your dummy project, add a TStyleBook component to the form, and double click it to open the style book editor.

6. Click the ‘Load’ button and locate the extracted *.style file.

7. Click the ‘Save’ button and overwrite the extracted *.style file.

The reason for step (7) is because the style will be extracted in a binary format, and resaving in the style book editor will output to a textual one. More specifically, it will convert from the DFM binary format to the DFM textual one with just a simple header added – FMX being based on the VCL’s streaming system as much as the VCL itself.

Removing platform restrictions

If between running the dummy app on the device and loading the extracted style file into the style book you changed the active target platform, the style book editor will complain:

Style load error

To prevent this happening in future, once you have reselected the ‘correct’ target platform (Android in my case) and now successfully loaded the style file, select the style’s ‘description’ component in the Structure pane and press Delete:

Delete style description

Having clicked Apply (or Apply and Close), the style will also now load when you select a different platform (just remember that to make a style book active, the form’s StyleBook property needs to be assigned). Here for example is the Android style used when running on Windows:

Android style on Windows

While XE5 update 2 introduced a special ‘mobile preview’ style for testing mobile projects on Windows, you can’t beat having the style that will actually be used on a device I think.

Removing a FMX control’s size restrictions

One rather annoying aspect of FMX’s evolution has been a restriction on control sizes. The most notorious case is probably TGroupBox, which in XE4’s iOS support was bizarrely matched to Safari’s toolbar (i.e., a standard control with a non-standard height), rendering the thing useless. Nevertheless, the general rationale for fixed sizing is not ridiculous – different platforms have different standard sizes for buttons, toolbars and so forth, so it makes sense for FMX to assist the developer in ensuring these conventions are respected. Even so, the implementation taken has been distinctly sub-optimal in my view – in particular, I would have much preferred an AutoSize property that defaults to True. As things stand, standard sizes are instead hardcoded in internal style code and trump even the Align property.

For example, here’s a right-aligned TSpeedButton with its StyleLookup set to ‘composetoolbutton’ and parented to a TListBoxItem, running on an Android tablet. I’ve taken the screenshot as I am pressing the button down – notice how this clearly shows it to be taller than its parent:

Too tall TSpeedButton

Happily, this dubious behaviour can be prevented by overriding the control’s AdjustFixedSize method however. To implement this fix in the form of an interposer class, declare the following immediately before the form type:

type
  TSpeedButton = class(FMX.StdCtrls.TSpeedButton)
  protected
    procedure AdjustFixedSize(const Ref: TControl); override;
  end;

Next, implement the method like so:

procedure TSpeedButton.AdjustFixedSize(const Ref: TControl);
begin
  SetAdjustType(TAdjustType.None);
end;

Fix added, a speed button’s Align property will now be properly respected:

Fixed TSpeedButton

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.

If you write database applications in Delphi…

… you may want to contribute to the following thread on the Embarcadero forums:

https://forums.embarcadero.com/thread.jspa?messageID=620961&tstart=0#620961

Quote:

Hello All

At moment the DB RTL team is working on Data.DB.pas improvements. This is a good moment to bring our attention to the DB related issues registered in QC, which are important / valuable / annoying from your point of view. Please post your replies to this message with QC numbers and optional comments describing the importance of the issues. Thank you.


With best regards,
Dmitry Arefiev / FireDAC Architect

Native Android controls for XE5

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.

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.