Forum > Packages and Libraries
[SOLVED] Class-based properties not serializing
Wesbat:
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 [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---unit MyCustomComponent; {$mode ObjFPC}{$H+} interface uses Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs; type { TMyCustomComponent } TMyCustomComponent = class(TCustomControl) private protected public constructor Create(AOwner: TComponent); override; procedure Paint; override; published property Color; end; procedure Register; implementation procedure Register;begin RegisterComponents('My Components',[TMyCustomComponent]);end; { TMyCustomComponent } constructor TMyCustomComponent.Create(AOwner:TComponent);begin inherited Create(AOwner); SetInitialBounds(0, 0, 50, 50); Self.Color := clAqua;end; procedure TMyCustomComponent.Paint;var Bitmap: TBitmap;begin Bitmap := TBitmap.Create; try Bitmap.Height := Height; Bitmap.Width := Width; Bitmap.Canvas.Pen.Color := clBlack; Bitmap.Canvas.Brush.Color := Self.Color; Bitmap.Canvas.Rectangle(0, 0, Width, Height); Canvas.Draw(0, 0, Bitmap); finally Bitmap.Free; end;end; 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 [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- TSpamStyle = Class FBackColor: TColor; FBorderColor: TColor; FTextColor: TColor; published property BackColor: TColor read FBackColor write FBackColor; property BorderColor: TColor read FBorderColor write FBorderColor; property TextColor: TColor read FTextColor write FTextColor; 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 [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- TMyCustomComponent = class(TCustomControl) private { new } FNormalStyle:TSpamStyle; protected public constructor Create(AOwner: TComponent); override; procedure Paint; override; { new } destructor Destroy; override; published property Color; { new } property NormalStyle: TSpamStyle read FNormalStyle write FNormalStyle; end;
I add a constructor and destructor to allocate and free the property instance:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---constructor TMyCustomComponent.Create(AOwner:TComponent);begin inherited Create(AOwner); SetInitialBounds(0, 0, 50, 50); Self.Color := clAqua; { new } Self.NormalStyle := TSpamStyle.Create; Self.NormalStyle.BackColor := clFuchsia; Self.NormalStyle.BorderColor := clBlack;end;
And adjust my Paint method to use the style:
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---procedure TMyCustomComponent.Paint;... Bitmap.Canvas.Pen.Color := Self.NormalStyle.BorderColor; Bitmap.Canvas.Brush.Color := Self.NormalStyle.BackColor; Bitmap.Canvas.Rectangle(0, 0, Width, Height);
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.
dsiders:
--- Quote from: Wesbat on November 06, 2024, 06:29:55 am ---
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} --- TSpamStyle = Class FBackColor: TColor; FBorderColor: TColor; FTextColor: TColor; published property BackColor: TColor read FBackColor write FBackColor; property BorderColor: TColor read FBorderColor write FBorderColor; property TextColor: TColor read FTextColor write FTextColor; end;
--- End quote ---
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
Thaddy:
--- Quote from: dsiders on November 06, 2024, 07:10:59 am ---If you want properties implemented using this type to be included in component streaming, the class ahould be derived from TPersistent... not TObject.
--- End quote ---
Or compile the class in {$M+} state, which will do the same for published properties, even if not derived from TPersistent.
--- Code: Pascal [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---{$push}{$M+}type TSpamStyle = Class FBackColor: TColor; FBorderColor: TColor; FTextColor: TColor; published property BackColor: TColor read FBackColor write FBackColor; property BorderColor: TColor read FBorderColor write FBorderColor; property TextColor: TColor read FTextColor write FTextColor; end;{$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.
Wesbat:
--- Quote from: dsiders on November 06, 2024, 07:10:59 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
--- End quote ---
Brilliant, exactly what was missing. Thanks for the help dsiders!
Thaddy:
See my remark. dsiders is correct, but you can do it another way as I explained.
Navigation
[0] Message Index
[#] Next page