Owner Draw Elements in Rad Studio 10.2.2’s new Themed IDE

By | December 23, 2017

The following are a series of notes regarding owner drawn elements in the new RAD Studio 10.2.2 with theming enabled (more specifically, the Dark theme).

Do NOT use Themes.StyleServices to get theme styling information, instead get the IDE’s instance of it’s theming service through the IOTAIDEThemingServices interface as shown below (I’m not sure why the IDE doesn’t use the global Themes.StylesServices interface but don’t try and change it else horrible things will happen).

Var
  ITS : IOTAIDEThemingServices;

Begin
  ...
  FStyleServices := Nil;
  If Supports(BorlandIDEServices, IOTAIDEThemingServices, ITS) Then
    If ITS.IDEThemingEnabled Then
      FStyleServices := ITS.StyleServices;
  ...
End;

FStyleServices in the above is a private class field. You may want to put the above in your frame’s / form’s constructor but you may need to refresh this if a theme changes later on. For IDE Option frames this is okay as the theme will not change during the display process but for dockable forms and frames you will need to handle this differently (see further down).

So the way I’m handling the style services is as follows:

  If Assigned(FStyleServices) And FStyleServices.Enabled Then
    Begin
      ...
    End;

The next part is to get the correct colours for the rendering as follows:

Begin
  TargetCanvas.Font.Style := [];
  TargetCanvas.Font.Color := clWindowText;
  {$IFDEF DXE102}
  If Assigned(FStyleServices) And FStyleServices.Enabled Then
    TargetCanvas.Font.Color := FStyleServices.GetSystemColor(clWindowText);
  {$ENDIF}
  Case TBADIMetricColumns(Column) Of
    mcLength..mcToxicity: TargetCanvas.Font.Color := clBlack;
  End;
  NodeData := FVSTMetrics.GetNodeData(Node);
  If Not (NodeData.FNodeType In [ntMethod]) Then
    Begin
      If Column = 0 Then
        TargetCanvas.Font.Style := [fsBold];
    End;
  ...
End;

The above presets the canvas colour I need using standard Windows System colours (for non-themed earlier IDEs) and then inside an IFDEF, as this is only available in RAD Studio 10.2, I ask the style services for the alternate system colour and set it.

Depending upon the amount of owner drawing you are doing you may wish to cache the colours and only change them on a theme change.

Talking about theme changes, if you want to dynamically handle changes then you will probably need to install a style services notifier. This is a simple notifier with two methods that are fired before a theme change and after. I’ve defined mine as follows:

  TBADIStyleServicesNotifier = Class(TInterfacedObject, IOTANotifier, INTAIDEThemingServicesNotifier)
  Strict Private
    FUpdateProc : TUpdateProc;
  Strict Protected
    // IOTANotifier
    Procedure AfterSave;
    Procedure BeforeSave;
    Procedure Destroyed;
    Procedure Modified;
    // INTAIDEThemingServiceNotifier
    Procedure ChangedTheme;
    Procedure ChangingTheme;
  Public
    Constructor Create(Const UpdateProc : TUpdateProc);
  End;

The constructor takes a simple method reference which is called when a theme is changed and its there you need to updated anything you are caching including a new reference to the current style service from the IDE.

To install the notifier use the following code:

Begin
  Inherited Create(AOwner);
  ...;
  {$IFDEF DXE102}
  FStyleServicesNotifier := -1;
  If Supports(BorlandIDEServices, IOTAIDEThemingServices, ITS) Then
    FStyleServicesNotifier := ITS.AddNotifier(TBADIStyleServicesNotifier.Create(UpdateTreeViewColours));
  {$ENDIF}
  ...
End;

And to remove it use the following:

Begin
  ...
  {$IFDEF DXE102}
  If Supports(BorlandIDEServices, IOTAIDEThemingServices, ITS) Then
    ITS.RemoveNotifier(FStyleServicesNotifier);
  {$ENDIF}
  ...
  Inherited Destroy;
End;

Hope this short article helps get people up and running with Open Tools API solutions in the new Themed IDE.

Regards
Dave.