Search code examples
componentsc++buildervcl

How to customize the Caption property of a VCL component


(C++Builder 11)

Since I need to use a TSpeedButton with a caption over thr glyph (not on the top, on the bottom, on the left or on the right of the glyph) I followed Ted Lyngmo suggestion (Caption position in a TSpeedButton) to create a new VCL component. I have created a new component starting from TCustomSpeedButton and I have published only few property, not including the Caption. I have added my CustomCaption property and I have tried to override the Paint() method to write text in the middle of the button. The resulting component can load a glyph to show pressed and unpressed statuses and the content of CustomCaption is written in the middle of it.

But my CustomCaption is far from behave like the original Caption.

Firs of all, if I change the content of CustomCaption, at design time, there is no change in the button until I don't click on it (and I execute the Paint() method in the designer, I think...)

Then, if I change the font, at design time, my CustomCaption font doesn't change at all.

I tried to use CM_FONTCHANGED and CM_TEXTCHANGED messages but perhaps not in the right way.

This is the code

//header file
class PACKAGE TSpecialSpeedButton : public TCustomSpeedButton
{
    private:
        String fCustomCaption;
        //int fCustomCaptionTop, fCustomCaptionLeft;

        MESSAGE void __fastcall CMFontChanged(TMessage &Msg);
        MESSAGE void __fastcall CMTextChanged(TMessage &Msg);

    protected:
        void __fastcall Paint() override;


    public:
        __fastcall TSpecialSpeedButton(TComponent* Owner) override;

    __published:
        __property String CustomCaption = {read = fCustomCaption, write = fCustomCaption};
        //__property int CustomCaptionTop = {read = fCustomCaptionTop, write = fCustomCaptionTop};
        //__property int CustomCaptionLeft = {read = fCustomCaptionLeft, write = fCustomCaptionLeft};
        __property Glyph;
        __property GroupIndex = {default=0};
        __property Font;
        __property NumGlyphs = {default=1};
        __property OnClick;

    BEGIN_MESSAGE_MAP
            VCL_MESSAGE_HANDLER(CM_FONTCHANGED, TMessage, CMFontChanged)
            VCL_MESSAGE_HANDLER(CM_TEXTCHANGED, TMessage, CMTextChanged)
    END_MESSAGE_MAP(TCustomSpeedButton)

};

//cpp file
static inline void ValidCtrCheck(TSpecialSpeedButton *)
{
    new TSpecialSpeedButton(NULL);
}
//---------------------------------------------------------------------------
__fastcall TSpecialSpeedButton::TSpecialSpeedButton(TComponent* Owner)
    : TCustomSpeedButton(Owner)
{
    //fCustomCaptionTop = 0;
    //fCustomCaptionLeft = 0;

    Height = 50;
    Width = 50;
}
//---------------------------------------------------------------------------
namespace Tspecialspeedbutton
{
    void __fastcall PACKAGE Register()
    {
        TComponentClass classes[1] = {__classid(TSpecialSpeedButton)};
        RegisterComponents(L"My Components", classes, 0);
    }
}
//---------------------------------------------------------------------------

void __fastcall TSpecialSpeedButton::Paint()
{
    TRect PtRect;

    TCustomSpeedButton::Paint();

    PtRect.Left = 0;
    PtRect.Top = 0;
    PtRect.Right = Width;
    PtRect.Bottom = Height;

    Canvas->Font = Font;    //this seems to have no effect

    DrawTextW(Canvas->Handle, fCustomCaption.c_str(), fCustomCaption.Length(), &PtRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);

}
//---------------------------------------------------------------------------

void __fastcall TSpecialSpeedButton::CMFontChanged(TMessage &Msg)
{
    Invalidate();
}
//---------------------------------------------------------------------------

void __fastcall TSpecialSpeedButton::CMTextChanged(TMessage &Msg)
{
    Invalidate();
}
//---------------------------------------------------------------------------

I'm not able to manage the CustomCaption in the right way, that is as the original Caption. I have searched information over the internet, in the Embarcadero docwiki - Component Writer's Guide, in the files Vcl.Buttons.pas but I don't find the right way, someone can help me?


Solution

  • Embarcadero made some changes a while back that cause subtle problems with Windows resources such as TFont, TBrush, TPen, etc. The problem you're running into is that TFont.Assign doesn't always cause TCanvas to recreate the Windows font object. To force the font object to be recreated you need to force an internal call to TFont.Changed. One hacky way to do this is to assign an unwanted value to TFont.Color and then set it back to the color you really want. For example:

    Canvas->Font = Font;
    Canvas->Font->Color = clNone;
    Canvas->Font->Color = Font->Color;
    

    I'm not able to manage the CustomCaption in the right way

    I'm not sure what you mean by "in the right way". If you mean you want a change to CustomCaption to cause a redraw then add a writer for the property and then call Invalidate when the value is changed.

    protected:
      void __fastcall SetCustomCaption(String Value);
    
    __published:
      __property String CustomCaption = { read = fCustomCaption, write = SetCustomCaption };
    
    void __fastcall TSpecialSpeedButton::SetCustomCaption(String Value)
    {
      if (fCustomCaption != Value)
      {
        fCustomCaption = Value;
        Invalidate();
      }
    }
    

    CM_TEXTCHANGED is a Windows message sent when the inherited Text/Caption property is changed. Since you are not using that inherited property then you most likely won't need to process that message. Unless you want to always set it back to an empty string.