Recent

Author Topic: [SOLVED] Class-based properties not serializing  (Read 748 times)

Wesbat

  • New Member
  • *
  • Posts: 24
    • engrams.dev
[SOLVED] Class-based properties not serializing
« on: November 06, 2024, 06:29:55 am »
Hello.

I've been enjoying my time with Lazarus and I'm diving into the world of creating custom components.

I've come across a minor issue where the designer-set properties are not being serialized/saved into the LFM. Clearly I am missing an important step. I hope somebody could point out my mistake?

A positive test

To set the scene, here is a boiled-down component that works as expected. It paints it's own background using the default Color property. Installing this to the IDE and dropping it on a form, we see the Aqua square as expected (image A).

Code: Pascal  [Select][+][-]
  1. unit MyCustomComponent;
  2.  
  3. {$mode ObjFPC}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs;
  9.  
  10. type
  11.  
  12.   { TMyCustomComponent }
  13.  
  14.   TMyCustomComponent = class(TCustomControl)
  15.   private
  16.  
  17.   protected
  18.  
  19.   public
  20.     constructor Create(AOwner: TComponent); override;
  21.     procedure Paint; override;
  22.   published
  23.     property Color;
  24.   end;
  25.  
  26. procedure Register;
  27.  
  28. implementation
  29.  
  30. procedure Register;
  31. begin
  32.   RegisterComponents('My Components',[TMyCustomComponent]);
  33. end;
  34.  
  35. { TMyCustomComponent }
  36.  
  37. constructor TMyCustomComponent.Create(AOwner:TComponent);
  38. begin
  39.   inherited Create(AOwner);
  40.   SetInitialBounds(0, 0, 50, 50);
  41.   Self.Color := clAqua;
  42. end;
  43.  
  44. procedure TMyCustomComponent.Paint;
  45. var
  46.   Bitmap: TBitmap;
  47. begin
  48.   Bitmap := TBitmap.Create;
  49.   try
  50.     Bitmap.Height := Height;
  51.     Bitmap.Width := Width;
  52.     Bitmap.Canvas.Pen.Color := clBlack;
  53.     Bitmap.Canvas.Brush.Color := Self.Color;
  54.     Bitmap.Canvas.Rectangle(0, 0, Width, Height);
  55.     Canvas.Draw(0, 0, Bitmap);
  56.   finally
  57.     Bitmap.Free;
  58.   end;
  59. end;
  60.  
  61. end.

I want to style my component based on different states (Normal, Hover, Down, Disable). I create a class type that will provide the blueprint for this structure:

Code: Pascal  [Select][+][-]
  1.   TSpamStyle = Class
  2.     FBackColor: TColor;
  3.     FBorderColor: TColor;
  4.     FTextColor: TColor;
  5.   published
  6.     property BackColor: TColor read FBackColor write FBackColor;
  7.     property BorderColor: TColor read FBorderColor write FBorderColor;
  8.     property TextColor: TColor read FTextColor write FTextColor;
  9.   end;

For demonstration purposes let's focus on NormalStyle only, I add private storage for the value, and publish it as a property:

Code: Pascal  [Select][+][-]
  1.   TMyCustomComponent = class(TCustomControl)
  2.   private
  3.     { new }
  4.     FNormalStyle:TSpamStyle;
  5.   protected
  6.  
  7.   public
  8.     constructor Create(AOwner: TComponent); override;
  9.     procedure Paint; override;
  10.     { new }
  11.     destructor Destroy; override;
  12.   published
  13.     property Color;
  14.     { new }
  15.     property NormalStyle: TSpamStyle read FNormalStyle write FNormalStyle;
  16.   end;

I add a constructor and destructor to allocate and free the property instance:

Code: Pascal  [Select][+][-]
  1. constructor TMyCustomComponent.Create(AOwner:TComponent);
  2. begin
  3.   inherited Create(AOwner);
  4.   SetInitialBounds(0, 0, 50, 50);
  5.   Self.Color := clAqua;
  6.   { new }
  7.   Self.NormalStyle := TSpamStyle.Create;
  8.   Self.NormalStyle.BackColor := clFuchsia;
  9.   Self.NormalStyle.BorderColor := clBlack;
  10. end;

And adjust my Paint method to use the style:

Code: Pascal  [Select][+][-]
  1. procedure TMyCustomComponent.Paint;
  2. ...
  3.     Bitmap.Canvas.Pen.Color := Self.NormalStyle.BorderColor;
  4.     Bitmap.Canvas.Brush.Color := Self.NormalStyle.BackColor;
  5.     Bitmap.Canvas.Rectangle(0, 0, Width, Height);
  6.  

Compile and install into the IDE, form designer and runtime shows MyCustomComponent using the NormalStyle colors correctly (image B).

The problem

If I change the color of the NormalStyle property in the IDE designer, they are not saved into the LFM file, and at run-time my chosen colors are not used (image C).

I realize my component is missing some important information to tell the IDE how to serialize class TSpamStyle, but cannot find this information online.

What I tried

I have read the wiki page How_To_Write_Lazarus_Component and also Extending_the_IDE.

I looked at the source of other components to try identify what I am missing, but nothing stood out.

I implemented the property write as methods, but this seemed not to make any difference.

My mistake is likely silly, but I'd like to learn  :D

Edit: I attached the zipped example project to this post.
« Last Edit: November 06, 2024, 08:28:11 am by Wesbat »

dsiders

  • Hero Member
  • *****
  • Posts: 1291
Re: Class-based properties not serializing
« Reply #1 on: November 06, 2024, 07:10:59 am »

Code: Pascal  [Select][+][-]
  1.   TSpamStyle = Class
  2.     FBackColor: TColor;
  3.     FBorderColor: TColor;
  4.     FTextColor: TColor;
  5.   published
  6.     property BackColor: TColor read FBackColor write FBackColor;
  7.     property BorderColor: TColor read FBorderColor write FBorderColor;
  8.     property TextColor: TColor read FTextColor write FTextColor;
  9.   end;

If you want properties implemented using this type to be included in component streaming, the class ahould be derived from TPersistent... not TObject.

https://www.freepascal.org/docs-html/rtl/classes/tpersistent.html
Preview the next Lazarus documentation release at: https://dsiders.gitlab.io/lazdocsnext

Thaddy

  • Hero Member
  • *****
  • Posts: 16201
  • Censorship about opinions does not belong here.
Re: Class-based properties not serializing
« Reply #2 on: November 06, 2024, 08:15:42 am »
If you want properties implemented using this type to be included in component streaming, the class ahould be derived from TPersistent... not TObject.
Or compile the class in {$M+} state, which will do the same for published properties, even if not derived from TPersistent.
Code: Pascal  [Select][+][-]
  1. {$push}{$M+}
  2. type
  3.  TSpamStyle = Class
  4.     FBackColor: TColor;
  5.     FBorderColor: TColor;
  6.     FTextColor: TColor;
  7.   published
  8.     property BackColor: TColor read FBackColor write FBackColor;
  9.     property BorderColor: TColor read FBorderColor write FBorderColor;
  10.     property TextColor: TColor read FTextColor write FTextColor;
  11.   end;
  12. {$pop}
The only difference is that such a class does not support IFpObserved, but that is not directly related to published and component streaming.
The above example makes the published properties persist.(generates basic RTTI, which makes it streamable)
Furthermore, also for any children derived from this class.
« Last Edit: November 06, 2024, 08:28:43 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

Wesbat

  • New Member
  • *
  • Posts: 24
    • engrams.dev
Re: Class-based properties not serializing
« Reply #3 on: November 06, 2024, 08:27:41 am »

If you want properties implemented using this type to be included in component streaming, the class ahould be derived from TPersistent... not TObject.

https://www.freepascal.org/docs-html/rtl/classes/tpersistent.html

Brilliant, exactly what was missing. Thanks for the help dsiders!

Thaddy

  • Hero Member
  • *****
  • Posts: 16201
  • Censorship about opinions does not belong here.
Re: [SOLVED] Class-based properties not serializing
« Reply #4 on: November 06, 2024, 08:29:39 am »
See my remark. dsiders is correct, but you can do it another way as I explained.
If I smell bad code it usually is bad code and that includes my own code.

Wesbat

  • New Member
  • *
  • Posts: 24
    • engrams.dev
Re: Class-based properties not serializing
« Reply #5 on: November 06, 2024, 08:30:20 am »
Or compile the class in {$M+} state, which will do the same for published properties, even if not derived from TPersistent.

Ah this is very good to know too, now I have two solutions. Thanks very much Thaddy.

 

TinyPortal © 2005-2018