I'm writing a non-windowed VCL control that contains a TColor
array property, called Colors[]
. The Colors[]
property can be used to set the color of different parts of the control at run-time. The control will also get a property editor to set the Colors[]
property at design-time.
I know how to store array properties, but I've never done this for TColor
values. Normally, TColor
properties are stored as either a color constant (e.g. clBlack
or clWindowText
) or a numeric value (for custom colors).
TReader
and TWriter
have methods to read and write integers and strings, but not TColor
values. I looked at the source code of TPersistent
, but couldn't find how it reads and writes TColor
values.
Here's an abstract of my code:
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();
}
The Count
property specifies the number of different parts in the control, which is also the number of TColor
values stored in the FColors
array. The FColors
array is only allocated when the Colors[]
property is written to. If the FColors
array doesn't exist, reading the Colors[]
property returns the value of the control's Color
property. Setting the control's Color
property destroys the FColors
array.
How do I read and write TColor
values, so that color constants are preserved (and not converted to numeric values)?
TReader::NextValue()
returns the value type without incrementing the position in the DFM stream. The return value is used to differentiate between an identifier (registered color name) and an integer value (custom color).
The ReadColors()
and WriteColors()
methods thus become:
void __fastcall TMyControl::ReadColors (TReader* Reader)
{
Reader->ReadListBegin();
for (int i = 0; i < FCount; i++)
{
switch (Reader->NextValue())
{
case vaInt8:
case vaInt16:
case vaInt32:
Colors[i] = TColor(Reader->ReadInteger());
break;
case vaIdent:
{
int Value;
if (IdentToColor(Reader->ReadIdent(), Value))
{
Colors[i] = TColor(Value);
break;
}
}
default:
// Alternative: throw EReadError(SInvalidPropertyValue);
Reader->SkipValue();
}
}
Reader->ReadListEnd();
}
void __fastcall TMyControl::WriteColors (TWriter* Writer)
{
Writer->WriteListBegin();
for (int i = 0; i < FItemCount; i++)
{
String S;
if (ColorToIdent(Colors[i], S))
Writer->WriteIdent(S);
else
Writer->WriteInteger(Colors[i]);
}
Writer->WriteListEnd();
}
As an alternative to TReader::SkipValue()
, you can also throw an exception in case you encounter a value type other than an integer or an identifier.