Delphi FMX中TAlphaColor数组序列化及嵌套属性问题
背景
我正在开发Delphi FMX应用,需要给组件添加一个TAlphaColor数组类型的属性,定义为Colors: TArray<TAlphaColor>;。我想了解该属性在配套.fm文件中的存储方式,是否可以直接存储类似Colors: [125487, 159875]这样的数组值,以及具体格式。
我知道TPropertyEditor主要用于IDE自定义编辑器,不确定它是否会影响.fm文件的序列化/反序列化。
问题
- 是否可以在.fm文件中直接序列化
TAlphaColor数组? - 如果可以,如何在.fm文件中格式化数组以确保正确加载与保存?
后续尝试
我采用了Remy Lebeau建议的方案(稍作修改,使用WriteListBegin/WriteListEnd和ReadListBegin/ReadListEnd),当属性直接位于组件根节点时可以正常工作:
TALRectangle = class(TPersistent) private FColors: TArray<TAlphaColor>; procedure ReadColors(Reader: TReader); procedure WriteColors(Writer: TWriter); protected procedure DefineProperties(Filer: TFiler); override; published property Colors: TArray<TAlphaColor> read FColors write SetColors stored IsColorsStored; end;
对应的.fm文件内容:
object ALRectangle1: TALRectangle Colors = ( 123456, 123457 ) end;
但当属性位于嵌套组件结构中时(如下代码所示),读取属性时完全失败:
TALGradient = class(TPersistent) private FColors: TArray<TAlphaColor>; procedure ReadColors(Reader: TReader); procedure WriteColors(Writer: TWriter); protected procedure DefineProperties(Filer: TFiler); override; published property Colors: TArray<TAlphaColor> read FColors write SetColors stored IsColorsStored; end; TALBrush = class(TPersistent) private FGradient: TALGradient; published property Gradient: TALGradient read FGradient write SetGradient; end; TALRectangle = class(TALControl) private FFill: TALBrush; public property Fill: TALBrush read FFill write SetFill; end;
对应的.fm文件内容:
object ALRectangle1: TALRectangle Fill.Gradient.Colors = ( 123456, 123457 ) end;
请问嵌套属性序列化失败的原因是什么?
解答
1. 是否可在.fm文件中直接序列化TAlphaColor数组?
可以,但不能使用[125487, 159875]这种方括号格式。Delphi FMX的序列化系统对数组的标准存储格式是用圆括号()包裹元素,就像你在根节点属性中成功实现的那样。
2. fm文件中数组的正确格式化方式
数组属性在.fm文件中需遵循以下格式:
属性名 = ( 元素1, 元素2, ... )
元素可单独分行或放在同一行,换行更易读,元素间用逗号分隔即可。
嵌套属性序列化失败的原因
问题核心在于嵌套持久化对象的初始化时机:
- 当使用
DefineProperties注册自定义序列化方法时,Delphi的归档系统(TFiler)需要确保嵌套的持久化对象(此处为TALGradient)已被完全创建并初始化,才能调用其ReadColors方法。 - 你的代码中,
TALBrush的Gradient属性、TALRectangle的Fill属性如果没有在构造阶段自动创建实例,那么当归档系统尝试读取Fill.Gradient.Colors时,Gradient对象可能还未被初始化,直接导致读取失败。 - 此外,若父对象的属性加载逻辑存在问题,子对象的自定义序列化方法也无法被正确触发。
修复方案
自动创建嵌套持久化对象
修改TALBrush和TALRectangle的构造函数,确保嵌套对象在构造时自动实例化:TALBrush = class(TPersistent) private FGradient: TALGradient; public constructor Create; override; destructor Destroy; override; published property Gradient: TALGradient read FGradient write SetGradient; end; constructor TALBrush.Create; begin inherited; FGradient := TALGradient.Create; end; destructor TALBrush.Destroy; begin FGradient.Free; inherited; end; // 同理处理TALRectangle的Fill属性 TALRectangle = class(TALControl) private FFill: TALBrush; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property Fill: TALBrush read FFill write SetFill; end; constructor TALRectangle.Create(AOwner: TComponent); begin inherited; FFill := TALBrush.Create; end; destructor TALRectangle.Destroy; begin FFill.Free; inherited; end;检查Setter方法逻辑
确保SetGradient和SetFill方法正确处理对象所有权,避免空引用或重复创建问题。验证自定义序列化方法
确保TALGradient中的ReadColors和WriteColors方法正确处理列表读写:procedure TALGradient.ReadColors(Reader: TReader); var Count, I: Integer; Color: TAlphaColor; begin Reader.ReadListBegin; Count := Reader.ReadInteger; SetLength(FColors, Count); for I := 0 to Count - 1 do begin Color := TAlphaColor(Reader.ReadInteger); FColors[I] := Color; end; Reader.ReadListEnd; end; procedure TALGradient.WriteColors(Writer: TWriter); var I: Integer; begin Writer.WriteListBegin; Writer.WriteInteger(Length(FColors)); for I := 0 to High(FColors) do Writer.WriteInteger(Integer(FColors[I])); Writer.WriteListEnd; end;
内容的提问来源于stack exchange,提问作者zeus

