Extended TClipboard implementation for FMX and VCL (CCR.Clipboard)

I’ve just pushed to GitHub (first time, so be gentle…) an extended, multi-platform TClipboard implementation for newer Delphi versions:

https://github.com/chrisrolliston/CCR.Clipboard

Where the platform allows, supports delayed rendering, virtual files, change notifications, and inter-process TClipboard-based drag and drop. The code originates from the FMX TClipboard I published a few years back, though is much extended, and was refactored to support the VCL too (XE2+). For more info, check out the readme first…

https://github.com/chrisrolliston/CCR.Clipboard/blob/master/Readme.md

… followed by the wiki pages for discussion of individual features, together with known issues and limitations:

https://github.com/chrisrolliston/CCR.Clipboard/wiki

Disclaimer: supporting multiple FMX versions ain’t no fun, so if you come to try it in XE4 or whatever and have an issue, I may not be able to help you. Also, if you’re interested in drag and drop on OS X, consider using my code with any version lower than XE8 a ‘proof of concept’ only…

 

Advertisements

Loading the app’s icon into a TImage on Windows

Last time I showed how to load the icon of an FMX for Android app into a TImage. In practice, a lot of the debugging of a Delphi mobile app is likely to be with a Win32 ‘mobile preview’ build though… in which case it would be useful to load the icon when running on Windows as well.

To do that, you need to first add a *.ico version to the project. This is done via Project Options: select the Application item on the left, then All configurations – 32 bit Windows platform from the combo box at the top. Probably stating the obvious, but the icon loaded here should be a .ico version of the .png files set up for Android.

Once done, the most direct equivalent of the Android code I presented previously would be to use the LoadIcon Windows API to load the icon, then GetIconInfo and so forth to get the bitmap bits to copy to a FMX bitmap. Easier is to use the VCL however – since Vcl.Graphics.pas doesn’t bring in anything else of the VCL, there’s little point in deliberately avoiding it. As such, here’s an expanded version of the code I presented last time with a VCL-based Windows implementation added:

unit AppIconLoader;

interface

uses
  System.SysUtils, System.Classes, FMX.Graphics;

function GetAppIcon(Dest: TBitmap): Boolean;

implementation

uses
  {$IF DEFINED(ANDROID)}
  AndroidApi.JniBridge, AndroidApi.Jni.App, AndroidApi.Jni.GraphicsContentViewText, FMX.Helpers.Android,
  {$ELSEIF DEFINED(MSWINDOWS)}
  Vcl.Graphics,
  {$ENDIF}
  FMX.Surfaces;

function GetAppIcon(Dest: FMX.Graphics.TBitmap): Boolean;
{$IF DEFINED(ANDROID)}
var
  Activity: JActivity;
  Drawable: JDrawable;
  Bitmap: JBitmap;
  Surface: TBitmapSurface;
begin
  Result := False;
  Activity := SharedActivity;
  Drawable := Activity.getPackageManager.getApplicationIcon(Activity.getApplicationInfo);
  Bitmap := TJBitmapDrawable.Wrap((Drawable as ILocalObject).GetObjectID).getBitmap;
  Surface := TBitmapSurface.Create;
  try
    if not JBitmapToSurface(Bitmap, Surface) then
      Exit;
    Dest.Assign(Surface);
  finally
    Surface.Free;
  end;
  Result := True;
end;
{$ELSEIF DEFINED(MSWINDOWS)}
var
  Icon: TIcon;
  Stream: TMemoryStream;
begin
  Result := False;
  Stream := nil;
  Icon := TIcon.Create;
  try
    Icon.LoadFromResourceName(HInstance, 'MAINICON');
    if Icon.Handle = 0 then Exit;
    Stream := TMemoryStream.Create;
    Icon.SaveToStream(Stream);
    Stream.Position := 0;
    Dest.LoadFromStream(Stream);
  finally
    Icon.Free;
    Stream.Free;
  end;
  Result := True;
end;
{$ELSE}
begin
  Result := False;
end;
{$ENDIF}

end.

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

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