delphivcldelphi-10.4-sydney

How can I implement themes support in my own component?


I'm trying to make my own component that will be themed the same as the rest of application (the theme is set in Project > Options > Application > Appearance).

The control is derived from TWinControl (in the red frame below). How can I apply the application theme to my component? I will use on it a lot of stanard controls, like buttons, edits and so on.

image

I tried to find something in Google, but maybe my English is the problem with asking the right question :)


Solution

  • There is no special skin data specified for your component, so you need to pick parts of other similar components from VCL which look like yours. Then you need to look at the source code of this component and implement drawing the same with your specific changes. You didn't provide a detailed description of your component, so it all up to our fantasy. Let's say: you want to have something like TPanel with a custom tab in red rectangle in the middle of it. We will choose as parent TCustomControl (not TWinControl), because there we have implemented Canvas for our custom drawing and theme support. We will override UpdateStyleElements to react to themes changing and Paint for our drawing (TCustomPanel does the same, we take part of its paint function).

    unit Component1;
    
    interface
    
    uses
      System.SysUtils, System.Classes, vcl.Controls, vcl.Styles, WinApi.Windows,
      vcl.Themes, Vcl.Graphics, Vcl.ExtCtrls;
    
    type
      TComponent1 = class(TCustomControl)
      private
      protected
        procedure Paint; override;
        procedure UpdateStyleElements; override;
      end;
    
    procedure Register;
    
    implementation
    
    procedure Register;
    begin
      RegisterComponents('Samples', [TComponent1]);
    end;
    
    { TComponent1 }
    
    procedure TComponent1.Paint;
    const
      Alignments: array[TAlignment] of Longint = (DT_LEFT, DT_RIGHT, DT_CENTER);
      VerticalAlignments: array[TVerticalAlignment] of Longint = (DT_TOP, DT_BOTTOM, DT_VCENTER);
    var
      Rect: TRect;
      LColor: TColor;
      LStyle: TCustomStyleServices;
      LDetails: TThemedElementDetails;
      TopColor, BottomColor: TColor;
      BaseColor, BaseTopColor, BaseBottomColor: TColor;
      Flags: Longint;
    
      procedure AdjustColors(Bevel: TPanelBevel);
      begin
        TopColor := BaseTopColor;
        if Bevel = bvLowered then
          TopColor := BaseBottomColor;
        BottomColor := BaseBottomColor;
        if Bevel = bvLowered then
          BottomColor := BaseTopColor;
      end;
    
    begin
      //get rect, where we will drawing
      Rect := GetClientRect;
    
      //initilize colors
      BaseColor := Color;
      BaseTopColor := clBtnHighlight;
      BaseBottomColor := clBtnShadow;
    
      //get style
      LStyle := StyleServices(Self);
      if LStyle.Enabled and (seClient in StyleElements) then
      begin
        //get detail(background) of our style, which we will use
        LDetails := LStyle.GetElementDetails(tpPanelBackground);
        //check, if in this style our color is changed - we take it
        if LStyle.GetElementColor(LDetails, ecFillColor, LColor) and (LColor <> clNone) then
          BaseColor := LColor;
    
        //get detail(border) of our style, which we will use
        LDetails := LStyle.GetElementDetails(tpPanelBevel);
        //check, if in this style our color is changed - we take it
        if LStyle.GetElementColor(LDetails, ecEdgeHighLightColor, LColor) and (LColor <> clNone) then
          BaseTopColor := LColor;
        if LStyle.GetElementColor(LDetails, ecEdgeShadowColor, LColor) and (LColor <> clNone) then
          BaseBottomColor := LColor;
      end;
    
      //draw top border
      if BevelOuter <> bvNone then
      begin
        AdjustColors(BevelOuter);
        Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
      end;
    
      //if style does not draw borders - do it by ourselves
      if not (LStyle.Enabled and (csParentBackground in ControlStyle)) then
        Frame3D(Canvas, Rect, BaseColor, BaseColor, BorderWidth)
      else
        InflateRect(Rect, -Integer(BorderWidth), -Integer(BorderWidth));
    
      if BevelInner <> bvNone then
      begin
        AdjustColors(BevelInner);
        Frame3D(Canvas, Rect, TopColor, BottomColor, BevelWidth);
      end;
    
      with Canvas do
      begin
        if not LStyle.Enabled or not ParentBackground or not (seClient in StyleElements) or
           (not LStyle.IsSystemStyle and (Parent <> nil) and (Parent is TCustomPanel) and
           TCustomPanel(Parent).DoubleBuffered {and not CheckParentBackground(Parent)})
        then
        begin
          //set curect brush color
          Brush.Color := BaseColor;
          //and fill all client rect with it
          FillRect(Rect);
        end;
    
        //drawing red rectangle
        Brush.Style := bsClear;
        Pen.Color := clRed;
        InflateRect(Rect, -30, -30);
        Rectangle(Rect);
    
        if LStyle.Enabled then begin
          //draw
          //make tab smaller
          InflateRect(Rect, -10, -10);
          //move tab to bottom of recrangle
          OffsetRect(Rect, 0, 10 - 1);
          //get slyled tab
          LDetails := LStyle.GetElementDetails(ttTabItemSelected);
          //draw tab
          LStyle.DrawElement(Handle, LDetails, rect);
    
          //draw some text on tab
          Brush.Style := bsClear;
          Font := Self.Font;
          Flags := DT_EXPANDTABS or DT_SINGLELINE or
            VerticalAlignments[taVerticalCenter] or Alignments[taCenter];
          Flags := DrawTextBiDiModeFlags(Flags);
          if LStyle.Enabled and (seFont in StyleElements) then
          begin
            LDetails := LStyle.GetElementDetails(tpPanelBackground);
            if not LStyle.GetElementColor(LDetails, ecTextColor, LColor) or (LColor = clNone) then
              LColor := Font.Color;
            LStyle.DrawText(Handle, LDetails, 'CustomCaption', Rect, TTextFormatFlags(Flags), LColor)
          end
          else
            DrawText(Handle, Caption, -1, Rect, Flags);
        end;
      end;
    end;
    
    procedure TComponent1.UpdateStyleElements;
    begin
      inherited;
    end;
    end.
    

    Another standard part of styles you can find in module ‘Vcl.Themes’. Sources are simplified, but you can start from this point.

    P.S. If your component got more advanced drawing – you may use a style hook same as other advanced components. Look at ‘class constructor’ and ‘class destructor’. enter image description here

    enter image description here