Skip to content

Instantly share code, notes, and snippets.

@ericoporto
Forked from rounk-ctrl/Dark.md
Created October 8, 2022 16:12
Show Gist options
  • Save ericoporto/1745f4b912e22f9eabfce2c7166d979b to your computer and use it in GitHub Desktop.
Save ericoporto/1745f4b912e22f9eabfce2c7166d979b to your computer and use it in GitHub Desktop.
Win32 Dark Mode

Dark Mode APIs.

API Signatures.

ShouldAppsUseDarkMode()

Checks whether system is using dark mode or not.
Signature:

using fnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132

AllowDarkModeForWindow(In HWND hWnd, In bool allow)

Switches the control's theme to the dark variant if available.
Signature:

using fnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133

SetPreferredAppMode(In PreferredAppMode appMode)

Dark Context menu for the app. I don't know what else this does.
Signature(1903+):

using fnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903

PreferredAppMode enum

enum class PreferredAppMode
{
   Default,
   AllowDark,
   ForceDark,
   ForceLight,
   Max
};

Actually using them.

First we need to load uxtheme dll as a HMODULE.

HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);

Then we need to get the Process Address of the functions. We already know their ordinal numbers.
First initialize a instance of the signature.

fnSetPreferredAppMode SetPreferredAppMode;

then set the process address by using GetProcAddress

SetPreferredAppMode = (fnSetPreferredAppMode)GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));

You can do like this for any API. Dont forget to free it

FreeLibrary(hUxtheme);

Usage.

Enables dark context menus which change automatically depending on the theme.

SetPreferredAppMode(PreferredAppMode::AllowDark);

Dark Controls:

First we need to know which all controls support Dark Mode.
If you open aero.msstyles in msstylesEditor, you would be able to see a lot of DarkMode elements.

image
And no you cant just do SetWindowTheme([handle to the control], L"DarkMode_Explorer", NULL);

Button:
SetWindowTheme([handle to the control], L"Explorer", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
Edit:
SetWindowTheme([handle to the control], L"CFD", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
ComboBox:
SetWindowTheme([handle to the control], L"CFD", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
TreeView (maybe):
SetWindowTheme([handle to the control], L"Explorer", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
Dark Scrollbars:

For dark scrollbars you need to iat hook and change the scrollbar theme before its created.
First download this header file and add it to your project. IatHook.h
Then use this function before the creation of HWND.

void FixDarkScrollBar()
{
	HMODULE hComctl = LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
	if (hComctl)
	{
		auto addr = FindDelayLoadThunkInModule(hComctl, "uxtheme.dll", 49); // OpenNcThemeData
		if (addr)
		{
			DWORD oldProtect;
			if (VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), PAGE_READWRITE, &oldProtect))
			{
				auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) -> HTHEME {
					if (wcscmp(classList, L"ScrollBar") == 0)
					{
						hWnd = nullptr;
						classList = L"Explorer::ScrollBar";
					}
					return _OpenNcThemeData(hWnd, classList);
				};

				addr->u1.Function = reinterpret_cast<ULONG_PTR>(static_cast<fnOpenNcThemeData>(MyOpenThemeData));
				VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), oldProtect, &oldProtect);
			}
		}
	}
}
@ajtruckle
Copy link

ajtruckle commented Jan 31, 2025

Thank you for your code @ericoporto .

I am trying:

// Combo Box Control
SetWindowTheme(GetDlgItem(IDC_COMBO2)->GetSafeHwnd(), L"DarkMode_CFD", nullptr);

// Populate Combo Box control with text
for (int i = 1; i <= 50; ++i) {
	CString option;
	option.Format(L"Option %d", i);
	ComboBox_AddString(GetDlgItem(IDC_COMBO2)->GetSafeHwnd(), option);
}

And it looks like this on the dialog:

image

My question concerns when the dropdown is displayed:

image

How do we get the back to render the same as for the List Box?

image


I went to try the FixDarkScrollBar method, to see if it would fix it, but encountered two issues:

  1. _OpenNcThemeData is not defined.
  2. fnOpenNcThemeData is not defined.

I am not sure if I need to get that function operational for my CListBox scrollbars to turn dark?

@ajtruckle
Copy link

@ericoporto
I worked it out (atleast for combos etc.):

// Lambda function to set the theme for a combo box
auto SetComboBoxTheme = [](CWnd* pComboBox) {
	COMBOBOXINFO cbi;
	cbi.cbSize = sizeof(COMBOBOXINFO);

	if (GetComboBoxInfo(pComboBox->GetSafeHwnd(), &cbi)) {
		SetWindowTheme(cbi.hwndList, L"DarkMode_Explorer", nullptr);
	}
	};

// Use the lambda to set the theme for each combo box
SetComboBoxTheme(GetDlgItem(IDC_COMBO2));
SetComboBoxTheme(GetDlgItem(IDC_COMBO4));

image

@ericoporto
Copy link
Author

Unfortunately I didn't develop the code above, I just forked it to look at it later.

The reason I was looking into this is to provide dark mode themeing in Adventure Game Studio, here is the corresponding issue adventuregamestudio/ags#1968

One thing is the inside of the drop down we had to create our own version of the control that repaints itself. About the scrollbars we had not seriously investigated at the time but you can see some experimental commits linked in the above issue.

@ajtruckle
Copy link

Thanks for explaining! At least I got it working easily enough! One of the things that threw me was that I needed DarkMode_CFD. Not CDF. 😀

Now I need to look at the other controls, and the dreaded menu bar!

@ericoporto
Copy link
Author

If you get it working somewhere in a repository or have a smaller version of the code somewhere, I would love to have it to take a look and at least reference it later in our issue so we can see it when we get to it

@ajtruckle
Copy link

ajtruckle commented Jan 31, 2025

If you get it working somewhere in a repository or have a smaller version of the code somewhere, I would love to have it to take a look and at least reference it later in our issue so we can see it when we get to it

I already pasted it here. This code is for the combobox dropdown list scrollbars.

@ajtruckle
Copy link

Can you please confirm if these controls have bespoke values to pass to SetWindowTheme?

  • Progress Bar
  • Slider

They seem OK to be without any theming from what I can tell:

image

@ajtruckle
Copy link

Actually, scrollbars have come to bite me, for CEdit:

image

And also radio elements insist on using black text. Grrh!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment