Search code examples
c++c++buildervcl

How to store (stream) a TColor array property?


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)?


Solution

  • 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.