Recent

Author Topic: ThemeServices.OnThemeChange is not fired on light/dark theme toggling  (Read 2450 times)

AlexTP

  • Hero Member
  • *****
  • Posts: 2671
    • UVviewsoft
Win10.
Lazarus Git 'main'.

When I change normal colors to 'high contrast mode' in the OS Control Panel, WM_THEMECHANGED  is fired ok. So my app can react to it via ThemeServices.OnThemeChange (it is fired by Win32 WS from TWindowProcHelper.DoWindowProc which handles WM_THEMECHANGED).

But, when I toggle light/dark colors (in the Win10 control panel, via combobox), WM_THEMECHANGED is not fired.

@440bx, maybe you know why?

 So my app cannot react to dark-mode activation via ThemeServices.OnThemeChange.
« Last Edit: December 23, 2025, 02:10:26 pm by AlexTP »

440bx

  • Hero Member
  • *****
  • Posts: 6070
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #1 on: December 23, 2025, 07:45:19 pm »
@440bx, maybe you know why?
Off hand, I do not know why.  I very rarely use Win 10, I avoid it as much as I can.

I have a couple of questions for you, please refer to the attached picture.

In the picture I don't see a "high contrast mode".  I do see choices for light/dark mode.  My questions are: are you using that screen or something else ?  Where do you see "high contrast mode" ?.

Basically, if you tell me exactly what you're doing to switch modes in each case, I may be able to determine what Windows is doing in each case thus leading to an answer.  if you are not using that same "settings" screen, please provide complete instructions how to get to whatever you are using because I don't know my way around in Win 10 (as I said, that's a version of Windows I avoid as much as I can - I know its internals and don't want to have anything to do with it.)

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

AlexTP

  • Hero Member
  • *****
  • Posts: 2671
    • UVviewsoft
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #2 on: December 24, 2025, 09:08:49 am »
I use the "high contrast" search words in the Control Panel and I can find this page: attached.

About light/dark: I used "colors" search word and found Colors page.
Attached.
« Last Edit: December 24, 2025, 09:10:41 am by AlexTP »

440bx

  • Hero Member
  • *****
  • Posts: 6070
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #3 on: December 24, 2025, 09:37:39 am »
Alex, thank you.  That should be enough information.  I'm going to look into what Windows does in those cases and let you know what I find once I have something to tell.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Xenno

  • Jr. Member
  • **
  • Posts: 51
    • BS Programs
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #4 on: December 24, 2025, 11:20:41 am »
In Delphi, the TApplicationEvents has OnSettingChange that raised by WM_SETTINGCHANGE. In Lazarus, the TApplicationProperties does not not have similar event. In my experience, WM_SETTINGCHANGE with a registry check works better is responding light/dark theme changes. So, in Lazarus we need intercept WM_SETTINGCHANGE in WndProc.
Lazarus 4.0, Windows 10, https://www.youtube.com/@bsprograms

jamie

  • Hero Member
  • *****
  • Posts: 7518
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #5 on: December 24, 2025, 07:06:12 pm »
In Delphi, the TApplicationEvents has OnSettingChange that raised by WM_SETTINGCHANGE. In Lazarus, the TApplicationProperties does not not have similar event. In my experience, WM_SETTINGCHANGE with a registry check works better is responding light/dark theme changes. So, in Lazarus we need intercept WM_SETTINGCHANGE in WndProc.

 Which is why I wrote myself a little unit to make easier for message capturing like Delphi does. Just enable it in the Form.Create and list the messages you need and then define them in your form class the same way as Delphi would with the associated method to respond to the message.

 I've posted it here several times.

 Not sure if using the term "EZmessages" will find it.

Jamie

The only true wisdom is knowing you know nothing

440bx

  • Hero Member
  • *****
  • Posts: 6070
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #6 on: December 24, 2025, 07:43:35 pm »
Here are my findings...

Windows doesn't always send a WM_THEMECHANGED when a change occurs.  For instance, for a change in background color, it doesn't send that message, it sends a different message.

As an example, when changing the background color, this is what Windows sends to the app:
Quote
<000001> 001F0640 S WM_SYSCOLORCHANGE
<000002> 001F0640 R WM_SYSCOLORCHANGE
<000003> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_SETNONCLIENTMETRICS pszMetrics:00000000
<000004> 001F0640 S WM_GETICON nType:ICON_SMALL2
<000005> 001F0640 R WM_GETICON hicon:00000000
<000006> 001F0640 S WM_GETICON nType:ICON_SMALL
<000007> 001F0640 R WM_GETICON hicon:00000000
<000008> 001F0640 S WM_GETICON nType:ICON_BIG
<000009> 001F0640 R WM_GETICON hicon:00000000
<000010> 001F0640 S WM_WINDOWPOSCHANGING lpwp:0018FD0C
<000011> 001F0640 R WM_WINDOWPOSCHANGING
<000012> 001F0640 S WM_NCCALCSIZE fCalcValidRects:True lpncsp:0018FCE4
<000013> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F970
<000014> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000015> 001F0640 S message:0x0094 [Unknown] wParam:00000000 lParam:0018F920
<000016> 001F0640 R message:0x0094 [Unknown] lResult:00000000
<000017> 001F0640 S message:0x0094 [Unknown] wParam:00000000 lParam:0018F920
<000018> 001F0640 R message:0x0094 [Unknown] lResult:00000000
<000019> 001F0640 S message:0x0094 [Unknown] wParam:00000000 lParam:0018F920
<000020> 001F0640 R message:0x0094 [Unknown] lResult:00000000
<000021> 001F0640 R WM_NCCALCSIZE fuValidRect:0000 lpncsp:0018FCE4
<000022> 001F0640 S WM_WINDOWPOSCHANGED lpwp:0018FD0C
<000023> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F7A0
<000024> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000025> 001F0640 R WM_WINDOWPOSCHANGED
<000026> 001F0640 R WM_SETTINGCHANGE
<000027> 001F0640 S WM_SETTINGCHANGE wFlag:0000 pszMetrics:0008E2B8
<000028> 001F0640 R WM_SETTINGCHANGE
<000029> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_SETFLATMENU pszMetrics:0008E2D8
<000030> 001F0640 R WM_SETTINGCHANGE
<000031> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_SETICONTITLELOGFONT pszMetrics:0008E2D8
<000032> 001F0640 R WM_SETTINGCHANGE
<000033> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_ICONHORIZONTALSPACING pszMetrics:0008E2D8
<000034> 001F0640 R WM_SETTINGCHANGE
<000035> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_ICONVERTICALSPACING pszMetrics:0008E2D8
<000036> 001F0640 R WM_SETTINGCHANGE
<000037> 001F0640 S WM_WINDOWPOSCHANGING lpwp:0018FEF8
<000038> 001F0640 S WM_GETMINMAXINFO lpmmi:0018FBEC
<000039> 001F0640 R WM_GETMINMAXINFO lpmmi:0018FBEC
<000040> 001F0640 R WM_WINDOWPOSCHANGING
<000041> 001F0640 S WM_NCCALCSIZE fCalcValidRects:True lpncsp:0018FED0
<000042> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018FB5C
<000043> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000044> 001F0640 R WM_NCCALCSIZE fuValidRect:0000 lpncsp:0018FED0
<000045> 001F0640 S WM_WINDOWPOSCHANGED lpwp:0018FEF8
<000046> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F98C
<000047> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000048> 001F0640 R WM_WINDOWPOSCHANGED
<000049> 001F0640 S WM_SETTINGCHANGE wFlag:SPI_SETNONCLIENTMETRICS pszMetrics:0008E2C8
<000050> 001F0640 S WM_GETICON nType:ICON_SMALL2
<000051> 001F0640 R WM_GETICON hicon:00000000
<000052> 001F0640 S WM_GETICON nType:ICON_SMALL
<000053> 001F0640 R WM_GETICON hicon:00000000
<000054> 001F0640 S WM_GETICON nType:ICON_BIG
<000055> 001F0640 R WM_GETICON hicon:00000000
<000056> 001F0640 S WM_WINDOWPOSCHANGING lpwp:0018FD0C
<000057> 001F0640 R WM_WINDOWPOSCHANGING
<000058> 001F0640 S WM_NCCALCSIZE fCalcValidRects:True lpncsp:0018FCE4
<000059> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F970
<000060> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000061> 001F0640 R WM_NCCALCSIZE fuValidRect:0000 lpncsp:0018FCE4
<000062> 001F0640 S WM_WINDOWPOSCHANGED lpwp:0018FD0C
<000063> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F7A0
<000064> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000065> 001F0640 R WM_WINDOWPOSCHANGED
<000066> 001F0640 R WM_SETTINGCHANGE
<000067> 001F0640 S WM_SYNCPAINT
<000068> 001F0640 S WM_NCPAINT hrgn:00000001
<000069> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F554
<000070> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000071> 001F0640 S message:0x0093 [Unknown] wParam:00000000 lParam:0018F854
<000072> 001F0640 R message:0x0093 [Unknown] lResult:00000001
<000073> 001F0640 S message:0x0091 [Unknown] wParam:00000000 lParam:0018F854
<000074> 001F0640 R message:0x0091 [Unknown] lResult:00000000
<000075> 001F0640 S message:0x0092 [Unknown] wParam:00000000 lParam:0018F7EC
<000076> 001F0640 R message:0x0092 [Unknown] lResult:00000000
<000077> 001F0640 S message:0x0092 [Unknown] wParam:00000000 lParam:0018F7EC
<000078> 001F0640 R message:0x0092 [Unknown] lResult:00000000
<000079> 001F0640 S message:0x0092 [Unknown] wParam:00000000 lParam:0018F7EC
<000080> 001F0640 R message:0x0092 [Unknown] lResult:00000000
<000081> 001F0640 R WM_NCPAINT
<000082> 001F0640 S WM_ERASEBKGND hdc:3D0148D3
<000083> 001F0640 R WM_ERASEBKGND fErased:True
<000084> 001F0640 R WM_SYNCPAINT
<000085> 001F0640 P WM_PAINT hdc:00000000
Note that nowhere is a WM_THEMECHANGED message found.

Potentially useful messages sent to the app are: WM_SYSCOLORCHANGE and as @Xenno mentioned, WM_SETTINGCHANGE.

In this particular case, I'd choose the WM_SYSCOLORCHANGE because it is more specific. 

There are two ways I use to find out what messages a program I've written gets. 

1. use spy++ which comes with Visual Studio.  The downside is that to get it you must install Visual Studio but, the good news is, you don't need the VS installation for it to work.  That means you can install VS in some VM, copy the spyxxx executables to a directory outside the VM and from that moment, you can use spy++ anywhere and anytime you want (the executable is fully portable.)

2. I have code that can be included in any program that displays the messages a window receives.  To my dismay and disbelief, I've been unable to find it.  It's lost somewhere in the thousands of pieces of code I have laying around.   Because of that, I'm going to re-write it because spyxx does miss some messages but, it will likely be a few days before I have a basic module done.

Note that in the list of messages Windows sends, there are message $91, $92, $93 and $94.  Those are undocumented messages and they are reportedly used by the Windows Theme Manager.  The problem is, no one seems to know exactly what they mean and when/why exactly they get sent.  Because of that, it is probably unwise to depend on their presence to detect a theme change.

Lastly, there does not seem to be any way to determine what setting changed, only that something changed, not what.

HTH.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

AlexTP

  • Hero Member
  • *****
  • Posts: 2671
    • UVviewsoft
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #7 on: December 24, 2025, 09:22:27 pm »
@440bx, thanks for useful information.
I try to react to WM_SYSCOLORCHANGE & WM_SETTINGCHANGE but my Win10 program cannot show anything (and debugger don't stop in handlers).

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   SysUtils,
  9.   Messages,
  10.   Classes, Forms, Controls, Graphics, Dialogs, StdCtrls;
  11.  
  12. type
  13.  
  14.   { TForm1 }
  15.  
  16.   TForm1 = class(TForm)
  17.     ListBox1: TListBox;
  18.     procedure FormCreate(Sender: TObject);
  19.   private
  20.     cnt: integer;
  21.     procedure HandleOnThemeChange(Sender: TObject);
  22.     procedure Log(const s: string);
  23.     procedure WMSettingChange(var Message: TMessage); message WM_SETTINGCHANGE;
  24.     procedure WMSysColorChange(var Message: TMessage); message WM_SYSCOLORCHANGE;
  25.  
  26.   public
  27.  
  28.   end;
  29.  
  30. var
  31.   Form1: TForm1;
  32.  
  33. implementation
  34.  
  35. uses
  36.   Themes;
  37.  
  38. {$R *.lfm}
  39.  
  40. { TForm1 }
  41.  
  42. procedure TForm1.FormCreate(Sender: TObject);
  43. begin
  44.   ThemeServices.OnThemeChange:= @HandleOnThemeChange;
  45. end;
  46.  
  47. procedure TForm1.HandleOnThemeChange(Sender: TObject);
  48. begin
  49.   inc(cnt);
  50.   Log('OnThemeChange '+IntToStr(cnt)+': '+TimeToStr(now));
  51. end;
  52.  
  53. procedure TForm1.Log(const s: string);
  54. begin
  55.   listbox1.items.add(s);
  56.   listbox1.ItemIndex:= listbox1.items.count-1;
  57. end;
  58.  
  59. procedure TForm1.WMSettingChange(var Message: TMessage);
  60. begin
  61.   Log('WMSettingChange');
  62. end;
  63.  
  64. procedure TForm1.WMSysColorChange(var Message: TMessage);
  65. begin
  66.   Log('WMSysColorChange');
  67. end;
  68.  
  69. end.
  70.  

EDIT
Now I remembered that LCL filters out some WM_ messages, so I must add the WindowProc override.
« Last Edit: December 24, 2025, 09:58:12 pm by AlexTP »

440bx

  • Hero Member
  • *****
  • Posts: 6070
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #8 on: December 24, 2025, 10:10:30 pm »
You're welcome Alex.

I don't know anything about the LCL, therefore I cannot be of any help in that area.

Anyway, if you can install a copy of VS in some throw-away VM in order to get spy++ you would find it occasionally very useful to have that utility.  It's great to see what messages a window is getting and conversely what messages it isn't getting.  You don't have to keep VS, after you copy the versions of spy++ (32 and 64 bit) and its help files out of it, you can simply delete it (if you installed it in a temporary VM, just delete the VM.)

HTH.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Xenno

  • Jr. Member
  • **
  • Posts: 51
    • BS Programs
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #9 on: December 25, 2025, 06:32:09 am »
Until we could rely on WM, just (regularly) check the value of
HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme
for Application light/dark theme.

Code: Pascal  [Select][+][-]
  1. uses Registry;
  2.  
  3. function FncDarkModeActive: Boolean;
  4. const
  5.   KRegKey   = 'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\';
  6.   KRegValue = 'AppsUseLightTheme';
  7. var
  8.   Reg: TRegistry;
  9. begin
  10.   Result := False;
  11.   Reg := TRegistry.Create(KEY_READ);
  12.   try
  13.     Reg.RootKey := HKEY_CURRENT_USER;
  14.     if (Reg.KeyExists(KRegKey)) then begin
  15.       if (Reg.OpenKey(KRegKey, False)) then begin
  16.         try
  17.           if (Reg.ValueExists(KRegValue)) then
  18.             Result := (Reg.ReadInteger(KRegValue) = 0);
  19.         finally
  20.           Reg.CloseKey;
  21.         end;
  22.       end;
  23.     end;                
  24.   finally
  25.     Reg.Free;
  26.   end;  
  27. end;
       
Lazarus 4.0, Windows 10, https://www.youtube.com/@bsprograms

PascalDragon

  • Hero Member
  • *****
  • Posts: 6311
  • Compiler Developer
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #10 on: December 25, 2025, 03:51:02 pm »
When I change normal colors to 'high contrast mode' in the OS Control Panel, WM_THEMECHANGED  is fired ok. So my app can react to it via ThemeServices.OnThemeChange (it is fired by Win32 WS from TWindowProcHelper.DoWindowProc which handles WM_THEMECHANGED).

But, when I toggle light/dark colors (in the Win10 control panel, via combobox), WM_THEMECHANGED is not fired.

@440bx, maybe you know why?

 So my app cannot react to dark-mode activation via ThemeServices.OnThemeChange.

Windows does not provide a documented API for light/dark theme, thus it also doesn't provide a documented way to react to a change for it.

LeP

  • Full Member
  • ***
  • Posts: 125
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #11 on: December 25, 2025, 07:27:12 pm »
Windows does not provide a documented API for light/dark theme, thus it also doesn't provide a documented way to react to a change for it.

 :-\ https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-themechanged

Quote
WM_THEMECHANGED message

Broadcast to every window following a theme change event. Examples of theme change events are the activation of a theme, the deactivation of a theme, or a transition from one theme to another.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6311
  • Compiler Developer
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #12 on: December 27, 2025, 06:22:47 pm »
Windows does not provide a documented API for light/dark theme, thus it also doesn't provide a documented way to react to a change for it.

 :-\ https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-themechanged

Quote
WM_THEMECHANGED message

Broadcast to every window following a theme change event. Examples of theme change events are the activation of a theme, the deactivation of a theme, or a transition from one theme to another.

Theme <> Light/Dark Mode. The former is something much older while light/dark mode was essentially bolted on to the existing window components.

sgj

  • Newbie
  • Posts: 3
« Last Edit: January 18, 2026, 10:41:08 pm by sgj »

zeljko

  • Hero Member
  • *****
  • Posts: 1828
    • http://wiki.lazarus.freepascal.org/User:Zeljan
Re: ThemeServices.OnThemeChange is not fired on light/dark theme toggling
« Reply #14 on: January 20, 2026, 11:32:11 am »
Windows does not provide a documented API for light/dark theme, thus it also doesn't provide a documented way to react to a change for it.

 :-\ https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-themechanged

Quote
WM_THEMECHANGED message

Broadcast to every window following a theme change event. Examples of theme change events are the activation of a theme, the deactivation of a theme, or a transition from one theme to another.

Theme <> Light/Dark Mode. The former is something much older while light/dark mode was essentially bolted on to the existing window components.

Why not use WM_SYSCOLORCHANGE ? IMO, at least LCL should reload sys colors in this case and send invalidate to all opened forms ?

 

TinyPortal © 2005-2018