5

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

Pshemas
  • 146
  • 4
  • 1
    Windowed controls need to implement and register a [StyleHook](https://docwiki.embarcadero.com/Libraries/en/Vcl.Themes.TStyleHook) to react to themed-based messages and drawing. Use [`TStyleManager`](https://docwiki.embarcadero.com/Libraries/en/Vcl.Themes.TStyleManager) and [`TStyleEngine`](https://docwiki.embarcadero.com/Libraries/en/Vcl.Styles.TStyleEngine) to interact with the theming system while custom-drawing your control's UI. – Remy Lebeau Mar 04 '23 at 20:13
  • Can you give me some sample how to do this? – Pshemas Mar 04 '23 at 20:24
  • 1
    Unfortunately, I cannot, as I have never tried to implement support for styles in any of my own components. You will have to find another resource that documents the process. Or read the VCL's source code, there are several style hooks being used by standard controls. – Remy Lebeau Mar 04 '23 at 21:38
  • If you need an example, you can look at VCL/FMX source code that is delivered with Delphi. – fpiette Mar 05 '23 at 08:21

1 Answers1

2

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

Softacom
  • 613
  • 2
  • 6