如何存储(流化)TColor数组属性并保留颜色常量?
我正在编写一个非窗口VCL控件,其中包含名为Colors[]的TColor数组属性,用于在运行时设置控件不同部位的颜色,同时要给控件配备属性编辑器,方便设计时设置Colors[]属性。
我知道怎么存储数组属性,但从没处理过TColor类型的值。通常TColor属性会存储为颜色常量(比如clBlack、clWindowText)或者数值(自定义颜色)。TReader和TWriter有读写整数和字符串的方法,但没有直接读写TColor的方法,我查了TPersistent的源码也没找到相关实现。
以下是我的代码摘要:
class TMyControl : public TGraphicControl { typedef TGraphicControl inherited; private: TColor* FColors; int FCount; TColor __fastcall GetColors (int Index); void __fastcall SetColors (int Index, TColor Value); void __fastcall ReadColors (TReader* Reader); void __fastcall WriteColors (TWriter* Writer); void __fastcall SetCount (int Value); void __fastcall CMColorChanged (TMessage &Msg); protected: virtual void __fastcall DefineProperties (TFiler *Filer); public: __fastcall TMyControl (TComponent *Owner); __fastcall ~TMyControl (); __property TColor Colors[int Index] = {read=GetColors, write=SetColors}; __published: __property Color; __property int Count = {read=FCount, write=SetCount, default=1}; BEGIN_MESSAGE_MAP MESSAGE_HANDLER(CM_COLORCHANGED, TMessage, CMColorChanged) END_MESSAGE_MAP(inherited) }; void ModifyExtents (TColor* &Array, int NewSize, TColor Default) { // Allocates/resizes 'Array' to contain 'NewSize' elements and initializes new elements with 'Default'. } __fastcall TMyControl::TMyControl (TComponent *Owner) : inherited(Owner) { } __fastcall TMyControl::~TMyControl () { delete[] FColors; } TColor __fastcall TMyControl::GetColors (int Index) { if (Index < 0 || Index >= FCount) throw Exception(SIndexOutOfRange); return(FColors ? FColors[Index] : Color); } void __fastcall TMyControl::SetColors (int Index, TColor Value) { if (Index < 0 || Index >= FCount) throw Exception(SIndexOutOfRange); if (!FColors) ModifyExtents(FColors, FCount, Color); if (FColors[Index] != Value) { FColors[Index] = Value; Invalidate(); } } void __fastcall TMyControl::SetCount (int Value) { if (Value < 0) Value = 0; if (FCount != Value) { if (FColors) ModifyExtents(FColors, Value, Color); FCount = Value; Invalidate(); } } void __fastcall TMyControl::CMColorChanged (TMessage &Msg) { inherited::Dispatch(&Msg); if (FColors) { delete[] FColors; FColors = NULL; } } void __fastcall TMyControl::DefineProperties (TFiler* Filer) { inherited::DefineProperties(Filer); Filer->DefineProperty("Colors", ReadColors, WriteColors, (FColors != NULL)); } void __fastcall TMyControl::ReadColors (TReader* Reader) { Reader->ReadListBegin(); for (int i = 0; i < FCount; i++) Colors[i] = Reader->Read...(); // ReadInteger, ReadString, ...? Reader->ReadListEnd(); } void __fastcall TMyControl::WriteColors (TWriter* Writer) { Writer->WriteListBegin(); for (int i = 0; i < FCount; i++) Writer->Write...(Colors[i]); // WriteInteger, WriteString, ...? Writer->WriteListEnd(); }
Count属性指定控件不同部位的数量,也就是FColors数组存储的TColor值数量。只有写入Colors[]属性时才会分配FColors数组;如果FColors不存在,读取Colors[]会返回控件的Color属性值。设置控件的Color属性会销毁FColors数组。
需要解决的核心问题:如何读写TColor值,以保留颜色常量(而非转换为数值)?
要实现TColor的持久化并保留颜色常量,需要利用VCL内置的ColorToIdent和IdentToColor函数,结合TReader/TWriter的字符串读写方法,具体实现如下:
1. 写入TColor值
在WriteColors方法中,先将TColor转换为对应的标识符字符串(如果是预定义颜色),否则转换为十六进制数值字符串,再用Writer->WriteString()写入:
void __fastcall TMyControl::WriteColors(TWriter* Writer) { Writer->WriteListBegin(); for (int i = 0; i < FCount; i++) { TColor ColorValue = Colors[i]; AnsiString ColorStr = ColorToIdent(ColorValue); if (ColorStr.IsEmpty()) { // 自定义颜色,转换为$00RRGGBB格式的十六进制字符串 ColorStr = AnsiString().sprintf("$%08x", ColorValue); } Writer->WriteString(ColorStr); } Writer->WriteListEnd(); }
2. 读取TColor值
在ReadColors方法中,先读取字符串,尝试用IdentToColor转换为TColor,失败则解析十六进制数值:
void __fastcall TMyControl::ReadColors(TReader* Reader) { Reader->ReadListBegin(); // 先读取列表实际元素数量,避免与当前FCount不匹配 int ListCount = Reader->ReadInteger(); if (ListCount != FCount) SetCount(ListCount); for (int i = 0; i < ListCount; i++) { AnsiString ColorStr = Reader->ReadString(); TColor ColorValue; // 优先解析为预定义颜色标识符 ColorValue = IdentToColor(ColorStr); if (ColorValue == clNone) { // 解析为十六进制数值,失败则使用控件默认Color try { ColorValue = (TColor)StrToInt(ColorStr); } catch (...) { ColorValue = Color; } } Colors[i] = ColorValue; } Reader->ReadListEnd(); }
3. 关键注意事项
ColorToIdent和IdentToColor在Graphics.hpp中声明,确保代码中包含该头文件。- 读取时必须先获取列表实际长度,再调整控件的
Count属性,避免数组越界或数据不匹配。 - 自定义颜色会以
$00RRGGBB格式存储,读取时能正确转换回原始数值。 - 该实现完全遵循VCL的持久化惯例,预定义颜色保留常量名,自定义颜色存储数值。
内容的提问来源于stack exchange,提问作者Martin Nijhoff

