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…

 

FMX TClipboard now supports iOS

I’ve just checked in a revision of my open source FMX TClipboard implementation that has an iOS backend. This supports the current rather than the FPC-based version of ‘Delphi for iOS’, however the Windows and OS X backends still compile with XE2 and above.

In essence, the new code wraps the native iOS clipboard API (UIPasteboard) and presents it in a fashion that closely follows the VCL clipboard interface, just like the existing Windows and OS X support did the same for desktop platforms. For some reason Apple in their wisdom decided to make the iOS clipboard API similar yet randomly different to the OS X one, so even though there’s not masses of code, it was a bit fiddly to implement. Anyhow, I’ve also knocked out a little demo similar to the previous desktop one:

TiOSClipboard demo

Using (say) Photos, you can copy an image to the clipboard and paste it into the demo. Conversely, you can from the demo itself copy either just the text entered, just the image, both the text and the image as two representations of the same clipboard item, or both the text and image as a custom clipboard format. The ‘List Formats on Clipboard’ button, as its name implies, then lists the formats currently on the clipboard. This is what I get after copying an image from Photos on the iOS simulator:

Format List

Technically, the iOS clipboard, like the OS X one, can have multiple items, each with multiple representations. Since the multiple-item concept doesn’t exist on Windows (indeed, it didn’t exist on OS X originally either), my class is only concerned with the first item, which is what most applications only bother with anyhow.

If you want the code, the SVN URL for it and a few other pieces is the following:

http://delphi-foundations.googlecode.com/svn/trunk/FMX%20Utilities/

The core files are now CCR.FMXClipboard.pas, CCR.FMXClipboard.Apple.pasCCR.FMXClipboard.iOS.pas, CCR.FMXClipboard.Mac.pas and CCR.FMXClipboard.Win.pas, and together they stand alone.

FMX anti-pattern: returning nil rather than raising an exception on an invalid state or arguments

In the Delphi RTL and VCL, a method called LoadFromFile or the like will raise an exception if the file doesn’t exist or is invalid. Similarly, if an object property getter implements a lazy-loading pattern (meaning, the value of the property will only be initialised when first needed), it will raise an exception if the situation is such that nothing valid can be loaded or initialised. This is pretty basic exception programming – when an error arises, the flow of the calling code is forcibly interupted with a clear account of what call was invalid. Imagine if (say) TBitmap.LoadFromFile didn’t raise an exception when the file name passed to it didn’t refer to a valid bitmap file – calling code could go happily on its way assuming a graphic has been loaded when it hasn’t. Likewise, what if the Items property getter for TList<T> just returned nil (or equivalent) with an out of bounds index? If T is a class type, then off-by-one errors in the calling code will end up with cryptic access violations later on that might be nowhere near where the erroneous call was actually made!

Alas, but such basic understanding of exception programming is not grasped by at least one developer on the FMX team. Repeatedly across the XE2, XE3 and now (I learn) XE4 releases, methods that an experienced Delphi programmer would expect to raise an exception when nothing valid can be returned do not. As soon as one case is fixed – e.g., the TFmxObject.Children property getter was done so in XE3, albeit implicitly – another one or two are added, and frankly, it needs to stop now. The latest cases I learn are the HScrollBar and VScrollBar property getters on TScrollBox – done properly in XE3, the anti-pattern has however been introduced in XE4, causing ‘random’ access violations in TMemo (the FMX TMemo inherits from TScrollBox).

Annoying FireMonkey buglet/oversight of the week

My personal FireMonkey buglet/oversight of the week is this: setting a TTextControl descendant’s Text property inside its constructor doesn’t do anything. For those who don’t know, TTextControl is the base class for things like TLabel, TListBoxItem and TExpander in FMX. Create custom descendants of these, then, and the following sort of code will not work:

type
  TMyExpander = class(TExpander)
  public
    constructor Create(const AOwner: TComponent;
      const AText: string); reintroduce;
  end;

constructor TMyExpander.Create(const AOwner: TComponent;
   const AText: string);
begin
  inherited Create(AOwner);
  Text := AText;
end;

The reason is quite simple: TTextControl sets a flag in Create that prevents changes to the Text property doing anything until that flag is reset in an override of AfterConstruction. In true ‘FM squared’ fashion, the code is nevertheless convoluted enough to give you RSI from having to press F7 so much in trying to track this down. Argh!!!!

XE3 and Subversion/Google Code

Just a small heads up, but it has come to my attention that the XE3 IDE’s Subversion (SVN) integration does not support the older, pre-v1.7 Subversion format, at least out of the box. While there are interop issues with recent versions of TortoiseSVN (the Subversion client I actually use) too, the XE3 IDE goes a step further by just hanging when the ‘Open From Version Control’ command is used against a repository in the older format.

While this in practice won’t bother many people, Google Code (a free-as-in-beer way to publish open source code) still uses Subversion v1.6… which means anything Delphi-related there whose author chose Subversion precisely because the Delphi IDE provides SVN support in the box may now look slightly foolish… Anyhow, this is all just a slightly roundabout way of saying I’ve added a ZIP of the sample code for my XE2 book here!

[Edit – my initial post was overly negative about TortoiseSVN (thanks to Robert Love and M J Marshall for correcting me in the comments). The issue is that TortoiseSVN v1.7x forces you to upgrade working copies from v1.6x to v1.7x, which means you can’t use both it and the XE or XE2 IDE interchangeably, assuming you haven’t modified the IDE’s behaviour to use the newer SVN DLLs. However, if you only use TortoiseSVN as your client, it can happily work with v1.6x servers. This though gives even less reason for the XE3 IDE to just hang!]

Writing a simple FireMonkey TListLayout implementation

In FireMonkey, the usual way to group controls is to use a ‘layout’ of some sort. Conceptually, a layout is just a container control with no visual appearance of its own, at least by default. This contrasts to something like a panel or group box, which is also a container but one that the user sees as such.

A few layout classes are provided with Delphi. The simplest is TLayout, which does nothing more than implement the basic defintion of a layout control; beyond it stands TScrollBox, TScaleLayout, TGridLayout and TFlowLayout. You can read about them in the help, though in a nutshell, they do the following –

  • TScrollBox shows scroll bars as its contents extend beyond its own visible client area (the scroll bars are auto-hidden if not needed)
  • TScaleLayout resizes its controls as it itself is resized
  • TGridLayout positions and sizes its constituent controls in fixed-sized cells
  • TFlowLayout positions controls like the words in a paragraph, first left to right (or right to left via a property setting), then top to bottom. Controls are then moved accordingly when the layout is resized.

While it isn’t a layout control strictly speaking, an honorary mention also goes to the FireMonkey TListBox, whose fixed-width items can host child controls.

That said, I was recently wanting a layout object to organise controls top aligned from from top to bottom. Unfortunately, none of the standard layout classes met my requirements, which were as thus:

  1. Each control should fill the parent’s client width, unless the standard Paddings and Margins properties indicate otherwise.
  2. However, a control’s height should be specific to the control.
  3. The first control would be located at the top of the parent, the second immediately below the first (perhaps with a standard gap), the third immediately below the second and so on.
  4. It should be easy to change the order of controls.
  5. Ideally a vertical scroll bar should show if the controls cannot fit.

Requirement (1) ruled out TFlowLayout, number (2) ruled out TListBox and TGridLayout, none of the requirements made TScaleLayout relevant, and only (5) could be serviced by TScrollBox. Writing a custom layout class for the task proved pretty easy however:

  1. Create a new package project, and set its ‘description’ to something appropriate under Project|Options, Description (e.g., ‘List Layout Control’).
  2. Add a new unit to the package, and add System.SysUtils, System.Classes and FMX.Types to the unit’s interface section uses clause.
  3. Following TFlowLayout and its peers, declare a class descending from TControl. Publish the usual FMX control properties inherited from (but not published by) the base class.
  4. Following TFlowLayout again, add a new published property called VerticalSpacing, typed to Single, and add overrides for the DoAddObject, DoInsertObject and DoRealign protected methods.
  5. Declare the usual Register procedure needed for registering a custom component with the IDE.

Following this, the unit’s interface section should look like this. I’ve also added a ComponentPlatforms attribute to say the class supports Windows and OS X (‘any’ target would be more exact to be honest):

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

type
  [ComponentPlatforms(pidWin32 or pidWin64 or pidOSX32)]
  TListLayout = class(TControl)
  strict private
    FVerticalGap: Single;
    procedure SetVerticalGap(const Value: Single);
  protected
    procedure DoRealign; override;
    procedure DoAddObject(AObject: TFmxObject); override;
    procedure DoRemoveObject(AObject: TFmxObject); override;
  published
    property Align;
    property Anchors;
    property ClipChildren;
    property ClipParent;
    property Cursor;
    property DesignVisible;
    property DragMode;
    property EnableDragHighlight;
    property Enabled;
    property Locked;
    property Height;
    property HitTest;
    property Margins;
    property Opacity;
    property Padding;
    property PopupMenu;
    property Position;
    property RotationAngle;
    property RotationCenter;
    property Scale;
    property TouchTargetExpansion;
    property VerticalGap: Single read FVerticalGap write SetVerticalGap;
    property Visible;
    property Width;
    property OnApplyStyleLookup;
    property OnDragEnter;
    property OnDragLeave;
    property OnDragOver;
    property OnDragDrop;
    property OnDragEnd;
    property OnClick;
    property OnDblClick;
    property OnMouseDown;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnPainting;
    property OnPaint;
    property OnResize;
  end;

procedure Register;

The implementation of Register is as you would expect if you’ve ever written a custom VCL control, namely a simple call to RegisterComponents. DoAddObject and DoInsertObject then just call the inherited implementation before requesting the control realigns its children. Lastly, the VerticalGap property setter assigns the backing field before requesting a realignment too:

procedure Register;
begin
  RegisterComponents('Samples', [TListLayout]);
end;

{ TListLayout }

procedure TListLayout.DoAddObject(AObject: TFmxObject);
begin
  inherited;
  Realign;
end;

procedure TListLayout.DoRemoveObject(AObject: TFmxObject);
begin
  inherited;
  Realign;
end;

procedure TListLayout.SetVerticalGap(const Value: Single);
begin
  if Value = FVerticalGap then Exit;
  FVerticalGap := Value;
  Realign;
end;

The final thing to implement is the DoRealign override. As a bit of an aside, DoRealign itself embodies the XE2 to XE3 FireMonkey transition (a lot of good work done, but a lot still to complete) in microcosm: in XE2, there was just Realign, which was a public, virtual method. As implemented in TControl, it did a whole load of checks to see whether controls should be realigned before finally doing the actual realigning. This was bad design, since if you wished to customise how realignment is performed in a descendant of TControl, you had to duplicate all those initial checks in your Realign override. In XE3, in contrast, Realign has been devirtualised and instead paired with a virtual, protected DoRealign method. In the new scheme, Realign still performs all the initial checks it did before, however it then delegates to DoRealign to do the actual repositioning and resizing. All well and good, but the refactoring wasn’t quite finished – to prevent the possibility of recursive calls to Realign/DoRealign, DoRealign still needs to set a protected FDisableRealign field to True, then reset it to False once it has finished. Really Realign should do that for you though, wrapping the FDisableAlign assignments in a try/finally block – if that were done, FDisableAlign could then be withdrawn into strict private scope. Nonetheless, it’s not a big issue.

Anyhow, here’s what my DoRealign implementation looks like:

procedure TListLayout.DoRealign;
var
  Control: TControl;
  NextY, StdWidth: Single;
begin
  if ControlsCount = 0 then Exit;
  FDisableAlign := True;
  try
    NextY := Margins.Top;
    StdWidth := Width - Margins.Left - Margins.Right;
    for Control in Controls do
      if Control.Visible then
      begin
        NextY := NextY + Control.Padding.Top;
        Control.SetBounds(Margins.Left + Control.Padding.Left,
          NextY, StdWidth - Control.Padding.Right -
          Control.Padding.Left, Control.Height);
        NextY := NextY + Control.Height +
          Control.Padding.Bottom + VerticalGap;
      end;
  finally
    FDisableAlign := False;
  end;
end;

If you’re following along, save everything, switch to the Release build configuration before adding and compiling for the Win64 and OS X target platforms (if you only have the Starter edition, that’s fine, however there won’t be any platforms beyond Win32 to compile for). The first time you compile the package there will be a prompt for adding fmx to the package’s requires clause – accept it. Then, toggle back to the default Win32 target, right click on the BPL’s node in the Project Manager, and choose Install. Finally, for each target platform, add the DCU output folder to the IDE’s search path (Tools|Options, Environment Options -> Delphi Options -> Library, Library Path); if desired, also add the .pas folder to the IDE’s browse path. E.g., if I were to save custom control units under E:\Delphi\Lib, this would give a default DCU output folder for 32 bit Windows of E:\Delphi\Lib\Win32\Release. If you toggle the project’s build configuration for Debug and recompile, you can also add the ..\Debug folders to the debug DCU search path as well. [In case it weren’t obvious, these instructions are in case you aren’t familiar with how to manually install a custom control – FMX or VCL – in the IDE. If you are, then there’s nothing particular to my example control, or shouldn’t be.] If all goes well, TListLayout should now be available in the Tool Palette when designing a form.

Now, the DoRealign implementation explicitly fulfils requirements (1) to (3) in my original list. Requirement (4) is also implicitly met, since our DoRealign lays out controls in the order they appear in the Controls array property, and that order can be changed by setting a sub-control’s Index as desired. So, if MyPanel is at the bottom of the layout control, setting its Index to 0 will move it to the top. Requirement (5) can then be met simply by nesting the TListLayout inside a TScrollBox, and setting its Align property to alTop:

TListLayout demo

Here, the form has a top-aligned TToolbar (StyleLookup set to ‘HeaderItemStyle’, as the default toolbar style looks pretty ugly IMO!), followed by a client-aligned TScrollbox with its Padding set to (2, 2, 2, 2). This then contains a top-aligned TListLayout with VerticalSpacing set to 4, with the layout itself containing three top-aligned panels, each of which has its Margins set to (8, 8, 8, 8), a left-aligned TLabel added and a client-aligned TEdit too.