Markdown components are compatible with light and dark modes. The TMDStyle component contains defaults for these two modes in the form of class properties. The SelectMode method is used to switch from one mode to another.

This is an alternative to using multiple TMDStyle components as explained in Project Appearance. Use either method as you prefer.

Finding out the current mode

Let's start by looking at how to find out what mode the operating system is currently using. To do this, you need to query the registry.

function IsDarkModeEnabled: boolean;
const
  Key   = 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
  Value = 'AppsUseLightTheme';
begin
  with TRegistry.Create do
  try
    RootKey := HKEY_CURRENT_USER;

    if OpenKeyReadOnly(Key) and ValueExists(Value) then
      Exit(ReadInteger(Value) = 0);
  finally
    Free;
  end;

  Result := FALSE;
end;

Notification

When the user changes the mode, Windows broadcasts a WM_SETTINGSCHANGE message to all top-level Windows of running apps with the section parameter of this message set to ImmersiveColorSet. Handle this message in your main form to switch the markdown styling mode along with the app style.

type
  TForm1 = class(TForm)
  private
    procedure WMSettingChange(var Message: TWMSettingChange); message WM_SETTINGCHANGE;
  end;

implementation

procedure TForm1.WMSettingChange(var Message: TWMSettingChange);
begin
  if SameText('ImmersiveColorSet', String(Message.Section)) then
    EnableDarkMode(IsDarkModeEnabled);
end;

See below for EnableDarkMode implementation.

Select mode

In our application we have added the Windows10 Dark theme (menu Project Options, Application Appearance). This is the style we will use for the dark mode. The light mode will be the default Windows theme.

uses System.Markdown;

procedure TForm1.EnableDarkMode(aEnable :boolean);
const
  Styles   :array[boolean] of string = ('Windows', 'Windows10 Dark');
  MDStyles :array[boolean] of TMDStyleMode = (smLight, smDark);
begin
  if TStyleManager.TrySetStyle(Styles[aEnable]) then
    MDStyle1.SelectMode(MDStyles[aEnable]);
end;

We will also activate the correct style during the creation of the main form or data module.

procedure TForm1.FormCreate(Sender: TObject);
begin
  EnableDarkMode(IsDarkModeEnabled);
end;

Code highlighting

The code highlighting presets also contain mode dependent settings. The current mode can be retrieved from the TMDStyle.Mode property. Here is how to handle the OnSyntaxHighlighting event:

uses System.Markdown.SyntaxHighlights;

procedure TForm1.MDStyle1SyntaxHighlighting(Sender: TObject; const aLanguage: string; var aStyles: TMDSyntaxStyles);
begin
if MatchText(aLanguage, ['Pascal', 'Delphi']) then
    aStyles := TMDSyntax.Pascal(MDStyle1.Mode);
end;

Images

As long as the images are monochrome and in SVG format, it is possible to adapt them to suit the theme.

Notes

  • SVG using Skia
    • With Delphi XE8 up to 11 you will need to install the Skia4Delphi package first.
    • You must distribute the sk4d.dll Skia library with your application regardless of the Delphi version.
  • SVG using Image32
    • you will need to download and extract the Image32 package. Then to add the Source and Source\Clipper2 folders to the EDI library path or to your project search path.


The TWICImage class helper declared in Vcl.Markdown.Skia or Vcl.Markdown.Image32 has a TAlphaColor argument passed to its load methods. This allows you to replace the color defined in the image with the color of your choice. Note that the color will not be replaced if it is set to TAlphaColors.Null. Handle the OnUnknownImage or OnUnknownImage64 as follow:

uses Vcl.Graphics, Vcl.Markdown.Image32 {or Vcl.Markdown.Skia};

procedure TForm1.MDStyle1UnknownImage(Sender: TObject; const aFileName: string; aSize: TSize; const aImage: TWICImage);
const
  Colors :array[TMDStyleMode] of TAlphaColor = (TAlphaColors.Null, TAlphaColors.White);
begin
  aImage.LoadFromFile(aFileName, aSize, Colors[MDStyle1.Mode]);
end;

Other image formats or color images can be filtered. Multiple images are added to the script and their display depends on a parameter appended to the link.

![img](BlackImage.jpg#light)
![img](WhiteImage.jpg#dark)

In your application, handle the OnImageValidate event as follow:

procedure TForm1.MDStyle1ImageValidate(Sender: TObject; const aName: string; aParams: array of string; var aAccept: Boolean);
begin
  aAccept := ((MDStyle1.Mode = smLight) and not MatchText('#dark', aParams)) or
             ((MDStyle1.Mode = smDark) and not MatchText('#light', aParams));
end;

In the example above, images are rendered if they do not specify the opposite mode or if the mode is undefined.

Example

The output will look like this:


Light mode

Dark mode