Monthly Archives: December 2015

Streaming non-published TPersistent Properties – A Better Way

In my last blog post, I discussed how to stream non-published properties, most importantly how to stream non-published TPersistent descendant properties.  The solution I discussed (using a TComponent intermediary so that you can call Writer.WriteComponent) is what you see most commonly as the solution on the web.  It works for the most part, but, in my opinion, there are several problems with it:

  • Does not work with the ObjectBinaryToText/ObjectTextToBinary methods
  • Inserts a fake path into the property name stream that can cause problems in some edge cases (for example, see the Inner.Address property in the output below)
  • Frankly, the resulting stream is ugly 🙂
object THouse
 Address = '123 Main St Anywhere, FL 32007'
 Price = 100000.000000000000000000
 DateBuilt = 28216.000000000000000000
 Bathrooms = 2.500000000000000000
 Bedrooms = 3
 NumOffers = 2
 TOuterComponent = Null
 Inner.Address = '456 Lonely Way Anywhere, FL 32006'
 Inner.Price = 85000.000000000000000000
 Inner.Date = 42339.000000000000000000
end

When I started trying to stream SVG documents into Delphi binary streams for the latest version of the RiverSoftAVG SVG Component Library, I ran hard into the brick wall of those edge cases I just mentioned.  🙁  Fortunately, there is a better way.  After a couple of days, I came up with this method, which I am really happy about.  I could kick myself though for using the old way for over a decade; I was never totally satisfied with it ( and intuitively, I knew it would cause me problems someday) but better late than never. 🙂

With a little spelunking through the TWriter/TReader code, you can see that the Delphi classes almost give us what you need.  There is a TWriter.WriteProperties public method.  There isn’t a TReader.ReadProperties equivalent.  There is a TReader.ReadProperty protected method.  There isn’t a TWriter public method for resetting the private FPropPath field to an empty string (we need this to make the property path name local to our TPersistent object).

However, with a judicious hack of the Delphi streaming system, and accessing some protected methods, we can create a nice, clean streaming system for non-published TPersistent objects.

The trick comes down to making the Delphi streaming system believe we are writing a collection.  When we write a collection to a stream, the TWriter class resets its FPropPath variable and allows us to write out “collection items” the way we want.

So what we end up with in our stream is we write out the vaCollection value type (using the protected TWriter.WriteValue method), and then write out our TPersistent properties inside list begin and end markers.  We need to crack the TReader to get access to the ReadProperty protected method and the TWriter to get access to the WriteValue protected method.  Here is what our new streaming code looks like:

type
 TReaderCrack = class(TReader);
 TWriterCrack = class(TWriter);

function ReadPersistent(Reader: TReader; Instance: TPersistent): TPersistent;
begin
 result := Instance;
 // read vaCollection
 Reader.CheckValue(vaCollection);
 // read list of items, in this case, only one, the top-level instance
 Reader.ReadListBegin;
 while not Reader.EndOfList do
 TReaderCrack(Reader).ReadProperty(Instance);
 Reader.ReadListEnd;
 Reader.ReadListEnd;
end;

procedure WritePersistent(Writer: TWriter; Instance: TPersistent);
begin
 // write out the Instance properties as a collection. This forces the writer
 // to zero out the property path
 TWriterCrack(Writer).WriteValue(vaCollection);
 // collection is a list of items
 // we are writing out a collection of one item, the Comparable object
 Writer.WriteListBegin;
 Writer.WriteProperties(Instance);
 Writer.WriteListEnd;
 Writer.WriteListEnd; // match vaCollection
end;

procedure THouse.DefineProperties(Filer: TFiler);
begin
 inherited DefineProperties(Filer);
 Filer.DefineProperty('NumOffers', ReadNumOffers, WriteNumOffers, NumOffers <> 0);
 Filer.DefineProperty('Comparable', ReadComparable, WriteComparable, True);
end;

procedure THouse.ReadComparable(Reader: TReader);
begin
 ReadPersistent(Reader, FComparable);
end;

procedure THouse.WriteComparable(Writer: TWriter);
begin
 WritePersistent(Writer, FComparable);
end;

The same trick can also be used to write out our non-published PurchaseHistory object list.

procedure THouse.ReadPurchaseHistory(Reader: TReader);
var
 Transaction: TTransaction;
begin
 PurchaseHistory.Clear;
 // read vaCollection
 Reader.CheckValue(vaCollection);
 while not Reader.EndOfList do
 begin
 Transaction := TTransaction.Create;
 PurchaseHistory.Add(Transaction);
 Reader.ReadListBegin;
 while not Reader.EndOfList do
 TReaderCrack(Reader).ReadProperty(Transaction);
 Reader.ReadListEnd;
 end;
 Reader.ReadListEnd;
end;

procedure THouse.WritePurchaseHistory(Writer: TWriter);
var
 i: Integer;
begin
 TWriterCrack(Writer).WriteValue(vaCollection);
 for i := 0 to PurchaseHistory.Count - 1 do
 begin
 Writer.WriteListBegin;
 Writer.WriteProperties(PurchaseHistory[i]);
 Writer.WriteListEnd;
 end;
 Writer.WriteListEnd; // match vaCollection
end;

Our final stream works with ObjectBinaryToText and ObjectTextToBinary.  The output of our stream is clean, and everything looks like normal, published properties:

object THouse
 Address = '123 Main St Anywhere, FL 32007'
 Price = 100000.000000000000000000
 DateBuilt = 28216.000000000000000000
 Bathrooms = 2.500000000000000000
 Bedrooms = 3
 NumOffers = 2
 Comparable = <
 item
  Address = '456 Lonely Way Anywhere, FL 32006'
  Price = 85000.000000000000000000
  Date = 42339.000000000000000000
 end>
 PurchaseHistory = <
 item
  Buyer = 'Baker Building Co'
  Seller = 'Anderson'
  Price = 45000.000000000000000000
  Date = 28260.000000000000000000
 end
 item
  Buyer = 'Anderson'
  Seller = 'Smith'
  Price = 60000.000000000000000000
  Date = 32654.000000000000000000
 end
 item
  Buyer = 'Smith'
  Seller = 'Johnson'
  Price = 90000.000000000000000000
  Date = 38629.000000000000000000
 end>
end

Writing out a TList in this case is easy as every item in the list is a TTransaction.  For heterogeneous lists (i.e., lists that contain different class types), it is still possible with just a little extra work.  We would write out the item class name first.  On reading the stream, you would read the class name, find the class, create it, and then read its properties.  However, I will leave this as an exercise for the reader or perhaps as a future blog post. 🙂

I hope you found this useful.  I know I did.  The full code, including a test program, is available to download.

Happy Holidays and Merry CodeSmithing! 🙂

Streaming non-published TPersistent properties

The Delphi streaming system is a marvel of design.  For most cases, streaming components to and from any stream (memory, file, string) just works.  You publish the properties you want to stream when designing your class, and the streaming system takes care of reading and writing your published properties.  Even streaming non-published properties is relatively easy.  However, there is one non-published property type that is much more difficult than it should be: object properties descended from TPersistent.

For the purposes of this blog post, we are going to define a THouse component.  This component has published properties like Address and Price as well as three unpublished properties:

  • NumOffers (simple integer property recording the number of buy offers we have on the house)
  • Comparable (an object property descended from TPersistent that holds the properties of a house that we think is comparable to the current house)
  • PurchaseHistory (a TObjectList property of TTransaction objects that record the purchase history of the house)

Here is the Delphi code for THouse, TComparable, and TTransaction:

uses
  Classes, SysUtils, Generics.Collections;

type
  TTransaction = class(TPersistent)
  private
    { private declarations }
    FDate: TDateTime;
    FPrice: Single;
    FSeller: String;
    FBuyer: String;
  protected
    { protected declarations }
  public
    { public declarations }
    procedure Assign(Source: TPersistent); override;
    constructor Create; overload; virtual;
    constructor Create( aSeller, aBuyer: String; aPrice: Single; aDate: TDate ); overload;
    function Clone: TTransaction; virtual;
  published
    { published declarations }
    property Buyer: String read FBuyer write FBuyer;
    property Seller: String read FSeller write FSeller;
    property Price: Single read FPrice write FPrice;
    property Date: TDateTime read FDate write FDate;
  end;
  TTransactions = TObjectList;

  TComparable = class(TPersistent)
  private
    { private declarations }
    FDate: TDate;
    FPrice: Single;
    FAddress: String;
  protected
    { protected declarations }
  public
    { public declarations }
    procedure Assign(Source: TPersistent); override;
    constructor Create; overload; virtual;
    constructor Create( aAddress: String; aPrice: Single; aDate: TDate ); overload;
  published
    { published declarations }
    property Address: String read FAddress write FAddress;
    property Price: Single read FPrice write FPrice;
    property Date: TDate read FDate write FDate;
  end;

  THouse = class(TComponent)
  private
    { private declarations }
    FPrice: Single;
    FDateBuilt: TDate;
    FBathrooms: Single;
    FPurchaseHistory: TTransactions;
    FComparable: TComparable;
    FNumOffers: Integer;
    FBedrooms: Integer;
    FAddress: String;
    procedure SetComparable(const Value: TComparable);
    procedure SetPurchaseHistory(const Value: TTransactions);
  protected
    { protected declarations }
  public
    { public declarations }
    procedure Assign(Source: TPersistent); override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property NumOffers: Integer read FNumOffers write FNumOffers;
    property Comparable: TComparable read FComparable write SetComparable;
    property PurchaseHistory: TTransactions read FPurchaseHistory write SetPurchaseHistory;
  published
    { published declarations }
    property Address: String read FAddress write FAddress;
    property Price: Single read FPrice write FPrice;
    property DateBuilt: TDate read FDateBuilt write FDateBuilt;
    property Bathrooms: Single read FBathrooms write FBathrooms;
    property Bedrooms: Integer read FBedrooms write FBedrooms;
  end;

Note that for the streaming system to work, it needs to be able to find the classes on reading and create them.  Therefore, the classes must be registered:

initialization
 RegisterClasses([THouse, TComparable, TTransaction]);
end.

If the THouse component is streamed as is, only the published properties are saved:

object THouse
 Address = '123 Main St Anywhere, FL 32007'
 Price = 100000.000000000000000000
 DateBuilt = 28216.000000000000000000
 Bathrooms = 2.500000000000000000
 Bedrooms = 3
end

Streaming non-published properties

To stream non-published properties, you override the TPersistent.DefineProperty method.  From the Delphi help:

By default, writing an object to a stream writes the values of all its published properties, and reading the object in reads those values and assigns them to the properties. Objects can also specify methods that read and write data other than published properties by overriding the DefineProperties method.

This process is fairly straightforward and, in general, the Delphi streaming classes (TFiler, TReader, and TWriter) make this easy to do.  There are 3 steps to defining any property to be published:

  1. Override the protected DefineProperties method and declare your non-published property
  2. Write a WriteXXX( Writer: TWriter ) method for each non-published property and write the property to the stream using TWriter.WriteXXX methods
  3. Write a ReadXXX( Reader: TReader ) method for each non-published property and read the property from the stream using TReader.ReadXXX methods

For the first step, the TFiler class provides two methods for “declaring” your non-published property and defining the reader and writer methods to use: DefineProperty and DefineBinaryProperty.  These methods accept the name of your “fake” property and two methods (one for reading, one for writing).  Finally, there is a parameter to tell the TFiler if the property should be written out.  Use this parameter to not write out properties that equal their defaults.  So, in our example to write out the NumOffers simple integer property, the DefineProperties method might look like this:

procedure THouse.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('NumOffers', ReadNumOffers, WriteNumOffers, NumOffers <> 0);
end;

Make sure you call the inherited DefineProperties method so that any ancestor classes can write our their fake properties.

Coding the read and write methods is easy for simple property types, the TReader and TWriter class provide many methods for simple types, such as TWriter.WriteInteger, TWriter.WriteBoolean and TWriter.WriteString.  The ReadNumOffers and WriteNumOffers methods look like:

procedure THouse.ReadNumOffers(Reader: TReader);
begin
  FNumOffers := Reader.ReadInteger;
end;

procedure THouse.WriteNumOffers(Writer: TWriter);
begin
  Writer.WriteInteger(NumOffers);
end;

Note that the TFiler class does the work of figuring out when to call your Reader and Writer methods so you don’t have to put in any special checks in your code.  For example, when reading the stream, when the Filer object detects your fake property name in the stream, it will position the stream and then call your Reader method.

Streaming non-published TPersistent properties

The Delphi streaming system starts to fail you when you want to write out non-simple published properties.  Unfortunately, the TWriter class does not have a WritePersistent method or something similar to write out our THouse.Comparable property.  Comparably, the TReader class lacks ReadPersistent method for reading these types back in.  So what are we to do?

If you search the web, you quickly see that there are 2 recommendations:

  1. Write the properties of the TPersistent object yourself (this is the worst way to do it as it is extremely tedious (as you have to write all the properties yourself), error prone (what happens if the class deletes a property at a later date?), and not maintainable (what happens if new properties are added to the class?  They are not streamed out).
  2. Create a TComponent wrapper class that has a published TPersistent property which you set to your TPersistent class and then stream the TComponent using Writer.WriteComponent.

The second method works and something similar is what I used for many years.  The idea is that you create a TComponent descendant whose only purpose is to read and write your TPersistent descendant class.  The Delphi streaming class has TWriter.WriteComponent and TReader.ReadComponent methods, which you can then use to stream your TPersistent property.

  TOuterComponent = class(TComponent)
  { Purpose: Special class for writing out TPersistent objects.  Allows the
    use of WriteComponent.  This component frees itself when it is loaded in order
    to avoid problems with TReader.  For TWriter, you should still free it }
  private
    FInner : TPersistent;
  protected
    procedure Loaded; override;
  public
  published
    property Inner : TPersistent Read FInner Write FInner;
  end;

{ TOuterComponent }

procedure TOuterComponent.Loaded;
begin
 inherited;
 {$IFDEF AUTOREFCOUNT}DisposeOf{$ELSE}Free{$ENDIF};
end;

procedure WritePersistent(Writer: TWriter; Instance: TPersistent);
// Saves a persistent object to a TWriter object
var
 Outer: TOuterComponent;
begin
 Outer := TOuterComponent.Create(nil);
 try
 Outer.Inner := Instance;
 // Write the outer object, it automatically writes the inner object
 Writer.WriteComponent( Outer );
 finally
 Outer.Free;
 end;
end;

function ReadPersistent(Reader: TReader; aObject: TPersistent): TPersistent;
// read a persistent object from a TReader object
var
 Outer: TOuterComponent;
begin
 Outer := TOuterComponent.Create(nil);
 Outer.Inner := aObject;
 // read the outer object, it automatically reads the inner object
 Reader.ReadComponent( Outer );
 result := Outer.Inner;
end;

With the TComponent class above and the two methods, code for writing a TPersistent property looks like this:

procedure THouse.DefineProperties(Filer: TFiler);
begin
 inherited DefineProperties(Filer);
 Filer.DefineProperty('NumOffers', ReadNumOffers, WriteNumOffers, NumOffers <> 0);
 Filer.DefineProperty('Comparable', ReadComparable, WriteComparable, True);
end;

procedure THouse.ReadComparable(Reader: TReader);
begin
 ReadPersistent(Reader, FComparable);
end;

procedure THouse.WriteComparable(Writer: TWriter);
begin
 WritePersistent(Writer, FComparable);
end;

In general, this is decent method for writing object properties.  It writes out all published properties of the TPersistent-descended class (even fake properties!) and does not need to change as your TPersistent class changes.

However, this method can quickly break down.  If you use the ComponentToStringProc and StringToComponentProc methods described in the Delphi Component To String example, it fails.  It can also fail in other specialized cases such as when you start nesting these properties (i.e., List of TPersistent which has TPersistent which as a list, etc).  Finally, quite honestly, it looks ugly to me in streams.  If you write out the stream as a string, the TPersistent-descended property does not look clean like the other properties:

object THouse
 Address = '123 Main St Anywhere, FL 32007'
 Price = 100000.000000000000000000
 DateBuilt = 28216.000000000000000000
 Bathrooms = 2.500000000000000000
 Bedrooms = 3
 NumOffers = 2
 TOuterComponent = Null
 Inner.Address = '456 Lonely Way Anywhere, FL 32006'
 Inner.Price = 85000.000000000000000000
 Inner.Date = 42339.000000000000000000
end

Fortunately, there is an even better way.  However, as I seem to be constitutionally incapable of writing short blog posts, I will stop here and leave you a teaser.  In my next blog post, I will detail how to write a TPersistent-like  and TList-like properties “the better way” and which provide clean output that can be passed through the Component To String example:

object THouse
 Address = '123 Main St Anywhere, FL 32007'
 Price = 100000.000000000000000000
 DateBuilt = 28216.000000000000000000
 Bathrooms = 2.500000000000000000
 Bedrooms = 3
 NumOffers = 2
 Comparable = <
 item
 Address = '456 Lonely Way Anywhere, FL 32006'
 Price = 85000.000000000000000000
 Date = 42339.000000000000000000
 end>
end

For now, Happy Holidays and Happy CodeSmithing!

When writing Cross-Library Code, Helpers can be your friend

Since XE2, I have been writing cross-platform (Windows, OSX, iOS, and Android) and “cross-library” (VCL and FMX) Delphi components.  My goal has always been to write as little unique platform or library code as possible (to reduce bugs and maintenance).  Sadly, Embarcadero (or Idera or whoever owns Delphi this week) did not put much emphasis on making code portable between VCL and FMX at first, though this has been improving with each version of Delphi since XE2.

For example, TVector, TMatrix and TPointF were not originally in the common run-time library (RTL) but in FMX only, which made no sense for code that had no library dependencies.  But though Embarcadero has been improving the RTL greatly, what about visual code?  If you use the end-point controls (TListBox, TEdit, etc), it is possible to mostly write code that uses them that would work for both VCL and FMX (e.g., ListBox1.Items.Add(‘Hello’)).  However, if you have to dive into using the Canvas, there has not been much, if any, progress made there.

For example, to write TPaintBox.OnPaint code for VCL, you might write code like this:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
 aCanvas: TCanvas;
begin
 aCanvas := (Sender as TPaintBox).Canvas;
 aCanvas.Font.Size := 20;
 aCanvas.TextOut(200,200,'Hello World!');
 aCanvas.Brush.Color := clRed;
 aCanvas.Pen.Width := 1;
 aCanvas.Rectangle(Rect(10,10,100,100));
 aCanvas.Brush.Color := clGreen;
 aCanvas.Pen.Width := 1;
 aCanvas.Ellipse(Rect(250,250,400,500));
 aCanvas.DrawFocusRect(Rect(250,250,400,500));
end;

Transferring this code to an FMX TPaintBox.OnPaint, we get a whole slew of errors:

[dcc32 Error] Unit2.pas(34): E2003 Undeclared identifier: 'TextOut'
[dcc32 Error] Unit2.pas(35): E2003 Undeclared identifier: 'Brush'
[dcc32 Error] Unit2.pas(35): E2003 Undeclared identifier: 'clRed'
[dcc32 Error] Unit2.pas(36): E2003 Undeclared identifier: 'Pen'
[dcc32 Error] Unit2.pas(37): E2003 Undeclared identifier: 'Rectangle'
[dcc32 Error] Unit2.pas(38): E2003 Undeclared identifier: 'Brush'
[dcc32 Error] Unit2.pas(38): E2003 Undeclared identifier: 'clGreen'
[dcc32 Error] Unit2.pas(39): E2003 Undeclared identifier: 'Pen'
[dcc32 Error] Unit2.pas(40): E2003 Undeclared identifier: 'Ellipse'
[dcc32 Error] Unit2.pas(41): E2003 Undeclared identifier: 'DrawFocusRect'

Unfortunately, the FMX TCanvas class does not have all the methods or properties that we have been used to with the VCL TCanvas.  At this point, what we usually do is start the laborious process of converting the code to FMX equivalents, e.g., Rectangle => FillRect, Brush => Fill, etc.  However, with class helpers we can make this process much less painful (though not always completely painless)

What are Class (Record) Helpers?

So what are class (record) helpers?  As the documentation states,

A class or a record helper is a type that - when associated with another class or a record - introduces additional method names and properties that may be used in the context of the associated type (or its descendants). Helpers are a way to extend a class without using inheritance, which is also useful for records that do not allow inheritance at all.

What this means is that it is a way to attach new methods and properties (but not new fields) to a class or record that can be used by calling code as if the new methods and properties were defined when the class was built.  These new methods and properties apply to the original class as well as any descendant classes as long as the class helper is in scope.

For example, to extend the VCL TBitmap class to have a Clear method, similar to the FMX TBitmap.Clear method, you can declare a class helper like this:

TBitmapHelper = class helper for TBitmap
public
  procedure Clear<span class="br0">(</span><span class="kw1">const</span> AColor<span class="sy1">:</span> TColor<span class="br0">)</span><span class="sy1">;</span> <span class="kw1">overload</span><span class="sy1">;
</span>  procedure Clear<span class="br0">(</span><span class="kw1">const</span> AColor<span class="sy1">:</span> TAlphaColor<span class="br0">)</span><span class="sy1">;</span> <span class="kw1">overload</span><span class="sy1">;
end;

[...]
procedure TBitmapHelper.Clear(const AColor: TColor);
begin
  Brush.Color := AColor;
  Brush.Style := bsSolid;
  Rectangle(Rect(0, 0, Width, Height));
end;

</span><span class="sy1">procedure TBitmapHelper.Clear(const AColor: TAlphaColor);
begin
  Brush.Color := RGB(TAlphaColorRec(AColor).R, TAlphaColorRec(AColor).G, TAlphaColorRec(AColor).B);
  Brush.Style := bsSolid;
  Rectangle(Rect(0, 0, Width, Height));
end;</span>

By adding this class helper for VCL TBitmap, you can now write:

Bitmap.Clear(clBlack);

Careful readers will notice that I added a procedure Clear( const AColor: TAlphaColor ) method.  This method gets to the purpose of this post.  By specifying a method with the exact same signature as the FMX TBitmap.Clear, your code that clears bitmaps can be written exactly the same with no changes whatsoever.  It can be a literal copy-and-paste.

Class (Record) Helpers for cross-library compatibility

Class (Record) Helpers are not intended to extend classes where you “own” and can modify the class code.  It is better in those cases to directly add the new methods and properties to the class itself.  However, when you do not own the code, class (record) helpers can be your friend and help you write portable code between VCL and FMX.  They can provide an elegant way to help write and maintain cross-library code, by either

  • Simplifying your code base that use common classes by adding new methods and properties to one library to achieve parity with the other library
  • Or, Attaching the same functionality to both libraries using the same code base

I’ll briefly discuss the second point to get it out of the way as it is not the focus of this blog post.  Class (Record) Helpers can help you add the same functionality to both libraries from one code base.  For example, say you wanted to give your developers a way to add a watermark to any bitmap for both VCL and FMX.  You can use a class helper to define an AddWatermark method to the TBitmap, for both VCL and FMX.  By carefully writing the method implementation, the same code could be used for both libraries (and then use #INCLUDE to scope the code for each library as discussed briefly in the digression part of this blog post and this Stack Overflow question)

Now onto the first point.  By using class helpers you can simplify your code base significantly by ensuring method signature and functional parity for the most part between the two libraries. For example, in our TPaintBox example above, the code can be significantly simplified by creating TCanvas helpers that add VCL TCanvas drawing functions to the FMX TCanvas and vice-versa.  By adding functions like DrawFocusRect, you can make the FMX TCanvas have methods exactly like the VCL TCanvas.  In this file, I have made class helpers for TBrushStroke and TCanvas.  Adding this file to the uses clause for my FMX PaintBox application, the errors have been significantly reduced.

[dcc32 Error] Unit2.pas(35): E2003 Undeclared identifier: 'clRed'
[dcc32 Error] Unit2.pas(38): E2003 Undeclared identifier: 'clGreen'

The way I fix these types of errors is that I define a clxRed and clxGreen that map to clRed/clGreen in VCL and TAlphaColorRec.Red/Green in FMX.  By changing the VCL/FMX code to use these constants, the code now looks like this and compiles in both libraries without changes:

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
 aCanvas: TCanvas;
begin
 aCanvas := (Sender as TPaintBox).Canvas;
 aCanvas.Font.Size := 20;
 aCanvas.TextOut(200,200,'Hello World!');
 <strong>aCanvas.Brush.Color := clxRed;</strong>
 aCanvas.Pen.Width := 1;
 aCanvas.Rectangle(Rect(10,10,100,100));
 <strong>aCanvas.Brush.Color := clxGreen;</strong>
 aCanvas.Pen.Width := 1;
 aCanvas.Ellipse(Rect(250,250,400,500));
 aCanvas.DrawFocusRect(Rect(250,250,400,500));
end;

Limitations of Class (Record) Helpers

Note that the example paint code above was carefully contrived to avoid the limitations of class (record) helpers and the differences between the two library architectures 🙂  The biggest problems are

  • Class Helpers have no fields you can add to the original class, so you cannot imitate some functionality.  For example, the VCL TCanvas has the MoveTo and LineTo methods.  The LineTo method draws a line from the previous MoveTo or LineTo call to a new point.  Since we cannot store the last point, we cannot imitate these methods.  However, at least in this case, we can use class helpers for both libraries to introduce methods to provide the equivalent functionality.  For example, by creating a DrawLine method that takes the start and end point and using that method in both VCL and FMX.
  • If the functionality doesn’t exist, you cannot imitate it.  For example, the VCL TBrush has the Style property that specify different fill patterns.  You could sort of imitate this behavior by setting the FMX Fill.Kind to TBrushKind.Bitmap and using a bitmap as the pattern but it is not the same.  In this case, I decided the effort wasn’t worth it.
  • Simple types can be challenging. For example, in FMX the canvas coordinates are singles, TPointF, and TRectF.  In VCL, they are Integer, TPoint, and TRect.  To get the center point in FMX, you would write code like MyPoint.X := (Rect.Right – Right.Left) / 2 + Rect.Left.  This code produces a single, which in VCL needs to rounded to truncated.  You CAN get around even this though with a little work.  In the RSGraphics.pas unit (FMX.RS.Graphics in FMX) that is in the RiverSoftAVG Common Classes Library, there are TCanvasPoint types that map to TPoint for VCL and TPointF for FMX.  There are also functions like CanvasDiv, which in FMX does the division above but in VCL uses a DIV to keep everything in Integer.  You can get the RSGraphics.pas unit by installing the RiverSoftAVG Charting Component Suite (or if you are an owner of any RiverSoftAVG product) as the RiverSoftAVG Common Classes Library  comes with it. 
  • The SCOPEDENUMS directive used in FMX can be a real headache.  Converting code from (VCL): Canvas.Brush.Style := bsSolid becomes in FMX: Canvas.Fill.Kind := TBrushKind.Solid (or Canvas.Brush.Kind if you are using my class helper).  You can get around it by defining an enumeration in VCL that looks like the FMX version, but it is an annoyance.
  • There can be only one class (record) helper per class or record.  This is the toughest limitation of all if you encounter it, and there is not a lot you can do about it.  If it is an Embarcadero/Idera class helper, you can copy and paste the class helper functionality into your own class helper, but if it is a third-party’s code, you may be out of luck.

Well, that is the end of another long blog post, but I hope you think it was worth it and that it can help you in your porting of code between VCL and FMX.

Happy CodeSmithing!