Dark mode class... lazarus / 34-Windows ---------------------------------------------------------------------- Espectr0 · 2022-05-15 14:59 Hola, I was thinking of making a class to implement "dark mode" in our applications in Windows 10 as I show in the images below (first image normal, second image dark mode). For controls that don't support dark mode, maybe a custom paint can be implemented? I await your comments, Greetings. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit UWSystem.DarkTheme; // ----------------------------------------------- ------------------------------ {$mode ObjFPC}{$H+} interface uses  Windows, Forms, Classes, SysUtils, dynlibs, Controls, StdCtrls; const  dwmapi = 'dwmapi.dll';  DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;  DWMWA_USE_IMMERSIVE_DARK_MODE = 20;   uxtheme = 'uxtheme.dll';   BackColor: Integer = $1E1E1E;  TextColor: Integer = $9B9B9B; type   { TUWDarkMode }   TUWDarkMode = class  private    FDWMlib  : TLibHandle;    FDWMfunc : function(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HRESULT; stdcall;    FUXlib   : TLibHandle;    FUXfunc  : function(hwnd: HWND; pszSubAppName: LPCWSTR; pszSubIdList: LPCWSTR): HRESULT; stdcall;     function IsWindows10OrGreater(const ABuild: Integer = -1): Boolean;  public    constructor Create(const AForm: TForm);    destructor Destroy; override;    function Loaded: Boolean;    function SetDarkMode(const AForm: TForm; const AValue: Bool = True): Boolean;  end; // ---------- ------------------------------------------------------------------- im plementation // ------------------------------------------------------ ----------------------- { TUWDarkMode } // --------------------------- -------------------------------------------------- constructor TUWDarkMode.Create(const AForm: TForm);begin  FDWMfunc := NIL;  FUXfunc  := NIL;   FDWMlib := LoadLibrary(dwmapi);  if FDWMlib <> 0 then Pointer(FDWMfunc) := GetProcAddress(FDWMlib, 'DwmSetWindowAttribute');   FUXlib := LoadLibrary(uxtheme);  if FUXlib <> 0 then Pointer(FUXfunc) := GetProcAddress(FUXlib, 'SetWindowTheme');   SetDarkMode(AForm);end; // ---------------------- ------------------------------------------------------- destructor TUWDarkMode.Destroy;begin  if FDWMfunc <> NIL then FDWMfunc := NIL;  if FDWMlib <> 0 then FreeLibrary(FDWMlib);   if FUXfunc <> NIL then FUXfunc := NIL;  if FUXlib <> 0 then FreeLibrary(FUXlib);   inherited Destroy;end; // ------------------------------------------------------ ----------------------- function TUWDarkMode.Loaded: Boolean;begin  Result := (FDWMlib <> 0) and (FUXlib <> 0)    and Assigned(FDWMfunc) and Assigned(FUXfunc);end; // ---------------------------------------- ------------------------------------- function TUWDarkMode.SetDarkMode(const AForm: TForm; const AValue: Bool = True): Boolean;var  attr: DWord;  i: Integer;begin  Result := False;  if AForm = NIL then Exit;   if IsWindows10OrGreater(17763) then  begin    attr := DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;    if IsWindows10OrGreater(18985) then attr := DWMWA_USE_IMMERSIVE_DARK_MODE;     FDWMfunc(AForm.Handle, attr, @AValue, SizeOf(AValue));  end;   with AForm do  begin    Color := BackColor;    Font.Color := TextColor;  end;   for i := 0 to AForm.ComponentCount-1 do  begin    if AForm.Components[i] is TWinControl then      FUXfunc((AForm.Components[i] as TWinControl).Handle, 'DarkMode_Explorer', NIL);  end;   Result := True;end; // --------------------------------------------------------- -------------------- function TUWDarkMode.IsWindows10OrGreater(const ABuild: Integer = -1): Boolean;begin  Result := (Win32MajorVersion >= 10) and (Win32BuildNumber >= ABuild);end; // ------------------------- ---------------------------------------------------- end. ---------------------------------------------------------------------- AlexTP · 2022-05-15 16:21 For MainMenu you can use: https://wiki.freepascal.org/Win32MenuStyler ---------------------------------------------------------------------- Espectr0 · 2022-05-15 20:42 Gracias @AlexTP, I will check it later. Thinking about the other controls (not menu), I have modified the class a bit and managed more colors except the CheckBox and RadioButton, can't change the font color? ---------------------------------------------------------------------- AlexTP · 2022-05-15 21:55 See the info here https://stackoverflow.com/questions/12248571/how-to-change-caption- font-color-on-tcheckbox-tradiobutton-tgroupbox ---------------------------------------------------------------------- Wallaby · 2022-05-16 12:29 I was able to implement nearly full dark mode support for my applications. For certain theme-drawn controls such as CheckBox/Radio, GroupBox, TabSeet, StatusBar and Progress bar you will need to go deeper and subclass them with SetWindowSubclass. In the subclass routine you'd need to intercept WM_PAINT and WM_ERASEBKGND and custom-draw these controls. ---------------------------------------------------------------------- Espectr0 · 2022-05-16 17:57 playing a little more I managed to paint the menu using TMenuInfo, except main menu form :S Also system menu is dark tool. ---------------------------------------------------------------------- Wallaby · 2022-05-16 21:51 Use this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i MenuInfo := Default(TMenuinfo);MenuInfo.cbSize := SizeOf(TMenuinfo);MenuInfo.fMask := MIM_BACKGROUND or MIM_APPLYTOSUBMENUS;MenuInfo.hbrBack := CreateSol idBrush(clBlack);Win32Check(SetMenuInfo(TMenu(Component).Handle, @MenuInfo)); ---------------------------------------------------------------------- Espectr0 · 2022-05-18 21:57 @Wallaby, I have it implemented but it doesn't paint the main menu bar... Anyway, I paste the code in case someone finds it useful and wants to improve it Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit UWSystem.DarkTheme; // ----------------------------------------------- ------------------------------ {$mode ObjFPC}{$H+} interface uses  Windows, Forms, Classes, SysUtils, Controls, StdCtrls, Menus; const  dwmapi_lib = 'dwmapi.dll';  DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;  DWMWA_USE_IMMERSIVE_DARK_MODE = 20;   uxtheme_lib = 'uxtheme.dll';   BackColor: Integer = $1E1E1E;  TextColor: Integer = $F0F0F0;  InputBackColor: Integer = $303030; type   // 1903 18362  TPreferredAppMode =  (    Default,    AllowDark,    ForceDark,    ForceLight,    Max  );   // 1809 17763  TShouldAppsUseDarkMode = function(): bool; stdcall; // ordinal 132  TAllowDarkModeForWindow = function(hWnd: HWND; allow: bool): bool; stdcall; // ordinal 133  TAllowDarkModeForApp = function(allow: bool): bool; stdcall; // ordinal 135, in 1809  TFlushMenuThemes = procedure(); stdcall; // ordinal 136  TRefreshImmersiveColorPolicyState = procedure(); stdcall; // ordinal 104  // 18334  TSetPreferredAppMode = function(appMode: TPreferredAppMode): TPreferredAppMode; stdcall; // ordinal 135, 1903   TDwmSetWindowAttribute = function(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HRESULT; stdcall;  TSetWindowTheme = function(hwnd: HWND; pszSubAppName: LPCWSTR; pszSubIdList: LPCWSTR): HRESULT; stdcall;   { TUWDarkMode }   TUWDarkMode = class  private    hLibDWM: TLibHandle;    DwmSetWindowAttribute: TDwmSetWindowAttribute;    hLibUX: HMODULE;    SetWindowTheme: TSetWindowTheme;     ShouldAppsUseDarkMode: TShouldAppsUseDarkMode;    AllowDarkModeForWindow: TAllowDarkModeForWindow;    AllowDarkModeForApp: TAllowDarkModeForApp;    FlushMenuThemes: TFlushMenuThemes;    RefreshImmersiveColorPolicyState: TRefreshImmersiveColorPolicyState;  SetPreferredAppMode: TSetPreferredAppMode;     procedure MyAllowDarkModeForApp(const allow: Bool);  public    constructor Create(const AForm: TForm);    destructor Destroy; override;    function Loaded: Boolean;    function SetDarkMode(const AForm: TForm; const AValue: Bool = True): Boolean;  end; procedure MyFillMenuBkg(const AMenu: TMenu);function IsWindows10OrGreater(const ABuild: Integer = 0): Boolean; // ------------------------------------ ----------------------------------------- implementation // ---------- ------------------------------------------------------------------- { TUWDarkMode } // ----------------------------------------------------- ------------------------ constructor TUWDarkMode.Create(const AForm: TForm);begin  DwmSetWindowAttribute := NIL;  SetWindowTheme  := NIL;   hLibDWM := LoadLibraryExW(dwmapi_lib, 0, LOAD_LIBRARY_SEARCH_SYSTEM32);  if hLibDWM <> 0 then Pointer(DwmSetWindowAttribute) := GetProcAddress(hLibDWM, 'DwmSetWindowAttribute');   hLibUX := LoadLibraryExW(uxtheme_lib, 0, LOAD_LIBRARY_SEARCH_SYSTEM32);  if hLibUX <> 0 then  begin    Pointer(SetWindowTheme) := GetProcAddress(hLibUX, 'SetWindowTheme');  Pointer(RefreshImmersiveColorPolicyState) := GetProcAddress(hLibUX, MakeIntResource(104));    Pointer(ShouldAppsUseDarkMode) := GetProcAddress(hLibUX, MakeIntResource(132));    Pointer(AllowDarkModeForWindow) := GetProcAddress(hLibUX, MakeIntResource(133));    Pointer(FlushMenuThemes) := GetProcAddress(hLibUX, MakeIntResource(136));     if not IsWindows10OrGreater(18362) then      Pointer(AllowDarkModeForApp) := GetProcAddress(hLibUX, MakeIntResource(135))    else      Pointer(SetPreferredAppMode) := GetProcAddress(hLibUX, MakeIntResource(135));  end;   SetDarkMode(AForm);end; // ------------ ----------------------------------------------------------------- dest ructor TUWDarkMode.Destroy;begin  if Pointer(DwmSetWindowAttribute) <> NIL then DwmSetWindowAttribute := NIL;  if hLibDWM <> 0 then FreeLibrary(hLibDWM);   if Pointer(SetWindowTheme) <> NIL then SetWindowTheme := NIL;   if Pointer(ShouldAppsUseDarkMode) <> NIL then ShouldAppsUseDarkMode := NIL;  if Pointer(AllowDarkModeForWindow) <> NIL then AllowDarkModeForWindow := NIL;  if Pointer(AllowDarkModeForApp) <> NIL then AllowDarkModeForApp := NIL;  if Pointer(FlushMenuThemes) <> NIL then FlushMenuThemes := NIL;  if Pointer(RefreshImmersiveColorPolicyState) <> NIL then RefreshImmersiveColorPolicyState := NIL;   if hLibUX <> 0 then FreeLibrary(hLibUX);   inherited Destroy;end; // --------------------- -------------------------------------------------------- function TUWDarkMode.Loaded: Boolean;begin  Result := (hLibDWM <> 0) and (hLibUX <> 0)    and Assigned(DwmSetWindowAttribute) and Assigned(SetWindowTheme);end; // ------------------------------------- ---------------------------------------- procedure TUWDarkMode.MyAllowDarkModeForApp(const allow: Bool);begin  if Assigned(AllowDarkModeForApp) then     AllowDarkModeForApp(allow)   else if Assigned(SetPreferredAppMode) then   begin     if allow then       SetPreferredAppMode(ForceDark)     else       SetPreferredAppMode(Default);   end;end; // ------------------------- ---------------------------------------------------- function TUWDarkMode.SetDarkMode(const AForm: TForm; const AValue: Bool = True): Boolean;var  attr: DWord;  C: TComponent;begin  Result := False;  if (AForm = NIL) or not Loaded then Exit;   MyAllowDarkModeForApp(AValue);  AllowDarkModeForWindow(AForm.Handle, AValue);  RefreshImmersiveColorPolicyState;  FlushMenuThemes;  SetWindowTheme(AForm.Handle, 'DarkMode_Explorer', NIL);   if IsWindows10OrGreater(17763) then  begin    attr := DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;    if IsWindows10OrGreater(18985) then attr := DWMWA_USE_IMMERSIVE_DARK_MODE;     DwmSetWindowAttribute(AForm.Handle, attr, @AValue, SizeOf(AValue));  end;   with AForm do  begin    Color    := BackColor;    Font.Color := TextColor;  end;   for C in AForm do  begin    if (C is TWinControl) then    begin      if not TWinControl(C).IsParentColor then        TWinControl(C).Color := InputBackColor;       if (C is TComboBox) or (C is TEdit) or (C is TMemo) then        SetWindowTheme(TWinControl(C).Handle, 'DarkMode_CFD', NIL)      else        SetWindowTheme(TWinControl(C).Handle, 'DarkMode_Explorer', NIL);    end    else if (C is TMenu) then      MyFillMenuBkg(TMenu(C));  end;   Result := True;end; // ----------------------------------------------- ------------------------------ procedure MyFillMenuBkg(const AMenu: TMenu);var  MenuInfo: TMenuInfo;begin  if AMenu <> NIL then  begin    FillByte(MenuInfo, SizeOf(TMenuInfo), 0);    MenuInfo.cbSize  := SizeOf(TMenuInfo);    MenuInfo.fMask   := MIM_BACKGROUND or MIM_APPLYTOSUBMENUS or MIM_STYLE;    MenuInfo.hbrBack := CreateSolidBrush(InputBackColor);    SetMenuInfo(AMenu.Handle, @MenuInfo);  end;end; // --------------------------------------------- -------------------------------- function IsWindows10OrGreater(const ABuild: Integer = 0): Boolean;begin  Result := (Win32MajorVersion >= 10) and (Win32BuildNumber >= ABuild);end; // ------------------------- ---------------------------------------------------- end. ---------------------------------------------------------------------- POA · 2025-08-16 17:15 Quote from: Wallaby on May 16, 2022, 02:29:05 pmI was able to implement nearly full dark mode support for my applications. For certain theme-drawn controls such as CheckBox/Radio, GroupBox, TabSeet, StatusBar and Progress bar you will need to go deeper and subclass them with SetWindowSubclass. In the subclass routine you'd need to intercept WM_PAINT and WM_ERASEBKGND and custom-draw these controls. @Wallaby, Can you share the source code for your dark mode app (since it appears you managed to get all the controls dark-themed)? Thanks