Add Dark Mode Support for TaskDialog (wxMSW) (PR #26311)

60 views
Skip to first unread message

Mohmed abdel-fattah

unread,
Mar 21, 2026, 5:54:36 AM (14 days ago) Mar 21
to wx-...@googlegroups.com, Subscribed
# Add Dark Mode Support for TaskDialog (wxMSW)

## Summary

This PR adds full dark mode theming support for the native Windows `TaskDialog` API as used by `wxMessageDialog` and `wxProgressDialog` on Windows. When the application enables dark mode via `wxApp::MSWEnableDarkMode()`, the TaskDialog and its child controls now render consistently with the rest of the application's dark theme instead of appearing with the default light system colours.


Fixes #26240
---

## Motivation

Previously, calling `wxApp::MSWEnableDarkMode()` successfully applied dark mode to all native `wxWidgets` controls and top-level windows, but the native `TaskDialog` (used by `wxMessageDialog` and `wxProgressDialog`) was left entirely unstyled. This created an obvious visual inconsistency: the rest of the application appeared dark while every message box and progress dialog retained bright white panels.

---

## Changes

### `src/msw/darkmode.cpp`

This is the core of the implementation. The following were added to the existing `wxMSWDarkMode` namespace:

**Colour palette (`TDDarkCol` namespace)**
A dedicated set of `constexpr COLORREF` constants matching `wxDarkModeSettings::GetColour()` values for the primary panel, secondary panel, footnote area, separator, and all text roles (instruction, content, expando, verification, footnote, radio button).

**Per-dialog state (`TDPageState` struct)**
A `thread_local` map (`tls_tdStates`) stores rendering state per `HWND`, including:
- Open theme handles (`HTHEME`) for `TaskDialog`, `TaskDialogStyle`, and `Button` parts
- GDI brushes for each background zone
- A UIA-backed layout cache (`std::vector<TDLayoutElement>`) with bounding rects and automation IDs for each UI element
- Mouse interaction state (hover/press/tracking) for interactive controls
- Logical state (`isExpanded`, `isChecked`) mirrored from window properties

**UIA layout cache (`TDBuildLayoutCache` / `TDUpdateLayoutCache`)**
UI Automation is used to walk the TaskDialog child tree and build a cache of element bounding rects and automation IDs. This avoids hooking individual child windows for hit testing and allows the paint routine to overdraw text and glyphs without relying on private Win32 control classes.

**Paint routines**
- `TDPaintPixelSwap` — On Windows 10 (no native dark TaskDialog theme), performs a pixel-by-pixel colour substitution on the buffered surface, replacing the light `TaskDialog` panel colours with the dark palette

Mohmed abdel-fattah

unread,
Mar 21, 2026, 6:05:58 AM (14 days ago) Mar 21
to wx-...@googlegroups.com, Subscribed
memoarfaa left a comment (wxWidgets/wxWidgets#26311)

Hi @vadz
Could you advise me on whether adding files to darkmode.cpp and darkmode.h is better, or creating separate files for dark mode taskdialog ?


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/c4102989690@github.com>

Mohmed abdel-fattah

unread,
Mar 21, 2026, 6:21:59 AM (14 days ago) Mar 21
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/788108b39d22c49311981530a45f4fc46dfeb35f/after/7b45662b75c00946e372df287e3b5e1e52cf5812@github.com>

Mohmed abdel-fattah

unread,
Mar 21, 2026, 6:48:24 AM (14 days ago) Mar 21
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/7b45662b75c00946e372df287e3b5e1e52cf5812/after/f04efe7bc501d43b89c5fdf6ae3d7eef22e52a90@github.com>

Mohmed abdel-fattah

unread,
Mar 21, 2026, 9:52:07 AM (13 days ago) Mar 21
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 68e0139 add luiautomationcore to makfile.gcc

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/f04efe7bc501d43b89c5fdf6ae3d7eef22e52a90/after/68e01396375ea43bd85efdf2e7cc4f4498d1cb13@github.com>

VZ

unread,
Mar 21, 2026, 11:11:36 AM (13 days ago) Mar 21
to wx-...@googlegroups.com, Subscribed
@vadz commented on this pull request.

Thanks a lot for all this work, it's pretty impressive — and the idea of using UI Automation for this is great.

This mostly looks fine to me, although I admit I didn't look into all the details, the only thing which definitely needs to be done is to replace ATL classes with wx ones, but hopefully it shouldn't be very difficult.

One other annoying thing is that it would be nice to avoid having to link with `uiautomation.lib`, as this breaks any existing makefiles for the applications using static wx libs with MinGW (for MSVC we could use `#pragma comment(lib)`...). What exactly do we need from it? If it's just the UUID, I'd simply hardcode it instead.

Concerning the question of whether it should all go to this file: I think it would be better to create a separate `darktaskdialog.cpp` or something like this.

> @@ -11,6 +11,7 @@
#define _WX_MSW_PRIVATE_DARKMODE_H_

#include "wx/settings.h"
+#include <commctrl.h>

Minor, but we usually do

```suggestion
#include "wx/msw/wrapcctl.h"
```

> @@ -32,6 +32,7 @@

#include "wx/msw/private/msgdlg.h"
#include "wx/evtloop.h"
+#include <wx/msw/private/darkmode.h>

Even more minor, but:


```suggestion
#include "wx/msw/private/darkmode.h"
```


> + // (The old code used TDN_DIALOG_CONSTRUCTED which fires too early
+ // for UI Automation to walk the child windows reliably.)

This was copy-pasted from `wxMessageDialog` but doesn't make sense here, this code never used `TDN_DIALOG_CONSTRUCTED`, so let's just remove it:


```suggestion
```

> +
+ case TDN_EXPANDO_BUTTON_CLICKED:
+ // Store the expanded state as a window property so the subclassed
+ // TaskPage panel can read it during WM_PAINT without a UIA call

Mohmed abdel-fattah

unread,
Mar 26, 2026, 9:31:10 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 4d057ed Fix wx style, use wxCOMPtr and wxBasicString and avoid having to link with uiautomation.lib.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/68e01396375ea43bd85efdf2e7cc4f4498d1cb13/after/4d057edc3ab5efd3ff961ff29967c74d22ffd8e8@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 10:15:19 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/4d057edc3ab5efd3ff961ff29967c74d22ffd8e8/after/6a4ac477ac7ff6d5c8395f657cc3ef741f7e854d@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 10:23:13 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 29190b8 fix error use of C++11 keywords nullptr

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/6a4ac477ac7ff6d5c8395f657cc3ef741f7e854d/after/29190b8801cabe2e1c3e8adf07fba9a40bb81440@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:05:37 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 48252ab aad wrapwin header try to fix error: ‘TASKDIALOGCONFIG’ does not name a type .

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/29190b8801cabe2e1c3e8adf07fba9a40bb81440/after/48252abf65e5088ef6278fbb31d011197f872b65@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:23:10 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/darkmode.cpp:

> @@ -44,6 +44,13 @@
 
 #include "wx/msw/private/darkmode.h"
 
+#include <uiautomation.h>
+#include <vssym32.h>
+#include <commctrl.h>
+#include <atlbase.h>    // CComPtr, CComBSTR

If use wxCOMPtr and wxBasicString look good now?


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/review/4015191202@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:23:32 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/darkmode.cpp:

> +    case TDLG_EXPANDOTEXT:         return TDDarkCol::kTextExpando;
+    case TDLG_VERIFICATIONTEXT:    return TDDarkCol::kTextVerify;
+    case TDLG_FOOTNOTEPANE:        return TDDarkCol::kTextFootnote;
+    case TDLG_EXPANDEDFOOTERAREA:  return TDDarkCol::kTextFtrExp;
+    case TDLG_RADIOBUTTONPANE:     return TDDarkCol::kTextRadio;
+    default:                       return TDDarkCol::kTextNormal;
+    }
+}
+
+// ============================================================================
+// Icon loading
+// ============================================================================
+
+static HICON TDLoadStockIcon(const TASKDIALOGCONFIG* cfg, bool isMain)
+{
+    if (!cfg) return nullptr;

If style look good now.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/review/4015183248@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:23:35 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/darkmode.cpp:

>  #include <memory>
 
 #if wxUSE_LOG_TRACE
 static const char* TRACE_DARKMODE = "msw-darkmode";
 #endif // wxUSE_LOG_TRACE
+#include <atlwin.h>

removed


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/review/4015186519@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:27:39 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/progdlg.cpp:

> +        // (The old code used TDN_DIALOG_CONSTRUCTED which fires too early
+        // for UI Automation to walk the child windows reliably.)

This not copy-pasted from wxMessageDialog from wxMessageDialog.
it's my old comment for don't use AllowForTaskDialog at TDN_DIALOG_CONSTRUCTED it's need TDN_CREATE


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/review/4015216462@github.com>

Mohmed abdel-fattah

unread,
Mar 26, 2026, 11:28:51 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/48252abf65e5088ef6278fbb31d011197f872b65/after/4ca42927fb0f2bbc478f0b5a68d5ba0c19ecb8f9@github.com>

VZ

unread,
Mar 27, 2026, 2:46:20 PM (7 days ago) Mar 27
to wx-...@googlegroups.com, Subscribed

@vadz commented on this pull request.

Thanks for the updates, but there are still some build problems. They look easy to fix (WINAPI missing in one place, just fix formatting — or, preferably, switch to using wxUxThemeHandle — in another) and I'll try to review this once again and merge as soon as this is done. Or I could just fix them myself if you prefer, please let me know.


In src/msw/darkmode.cpp:

> +            ct == UIA_ScrollBarControlTypeId || ct == UIA_PaneControlTypeId)
+        {
+            HWND hBtn = nullptr;
+            pChild->get_CurrentNativeWindowHandle(reinterpret_cast<UIA_HWND*>(&hBtn));
+            if (hBtn)
+            {
+                BSTR bId; pChild->get_CurrentAutomationId(&bId);
+                const std::wstring id(bId ? static_cast<LPCWSTR>(bId) : L"");
+                HWND hP = GetParent(hBtn);
+
+                if (ct == UIA_ProgressBarControlTypeId)
+                {
+                    HTHEME hCE = OpenThemeData(nullptr, L"DarkMode_CopyEngine::Progress");
+                    HTHEME hBase = OpenThemeData(nullptr, L"Progress");
+                    bool hasCE = (hCE && hCE != hBase);
+                    if (hCE) CloseThemeData(hCE); if (hBase) CloseThemeData(hBase);

This statement provokes gcc error in one of the CI builds. As mentioned before, this could be avoided entirely by using wxUxThemeHandle instead of manual open/close theme data calls.


In src/msw/darkmode.cpp:

> @@ -24,6 +24,7 @@
 
 // for compilers that support precompilation, includes "wx.h".
 #include "wx/wxprec.h"
+#include "wx/module.h"

Was this moved intentionally? Not really a problem, of course, but just looks weird.


In src/msw/darkmode.cpp:

> +
+    EndBufferedPaint(hbp, TRUE);
+}
+
+// ============================================================================
+// Subclass procedures
+// ============================================================================
+
+static LRESULT CALLBACK TDPageSubclassProc(
+    HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
+    UINT_PTR uId, DWORD_PTR)
+{
+    switch (msg)
+    {
+    case WM_ERASEBKGND:
+        return 1;

Minor, but this should be

⬇️ Suggested change
-        return 1;
+        return TRUE;

In src/msw/darkmode.cpp:

> +            FillRect(hdc, &rc, reinterpret_cast<HBRUSH>(dwRef)); return 1;
+        }
+        break;
+    case WM_CTLCOLORMSGBOX:
+    case WM_CTLCOLOREDIT:
+    case WM_CTLCOLORLISTBOX:
+    case WM_CTLCOLORBTN:
+    case WM_CTLCOLORDLG:
+    case WM_CTLCOLORSCROLLBAR:
+    case WM_CTLCOLORSTATIC:
+    {
+        HDC hdc = reinterpret_cast<HDC>(wParam);
+        COLORREF bg = TDDarkCol::kSecondary;
+        if (dwRef) { LOGBRUSH lb = {}; GetObject(reinterpret_cast<HBRUSH>(dwRef), sizeof(lb), &lb); if (lb.lbStyle == BS_SOLID) bg = lb.lbColor; }
+        SetBkColor(hdc, bg); SetTextColor(hdc, TDDarkCol::kTextNormal);
+        return reinterpret_cast<LRESULT>(dwRef ? reinterpret_cast<HBRUSH>(dwRef) : CreateSolidBrush(TDDarkCol::kSecondary));

Isn't this brush leaked?


In src/msw/darkmode.cpp:

> +    // Mouse interaction (message-driven, no polling)
+    bool tracking = false;
+    bool pressing = false;
+    int  hotIdx = -1;
+
+    // Logical dialog state
+    bool isExpanded = false;
+    bool isChecked = false;
+    bool defExpanded = false;
+    bool defChecked = false;
+
+    const TASKDIALOGCONFIG* pCfg = nullptr;
+
+    void CloseThemes()
+    {
+        if (hTD) { CloseThemeData(hTD);     hTD = nullptr; }

It would be really nice to avoid putting more than one statement per line, this improves readability a lot, in general, e.g.

⬇️ Suggested change
-        if (hTD) { CloseThemeData(hTD);     hTD = nullptr; }
+        if (hTD)
+        {
+            CloseThemeData(hTD);
+            hTD = nullptr;
+        }


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/review/4022985351@github.com>

Mohmed abdel-fattah

unread,
Mar 27, 2026, 5:59:58 PM (7 days ago) Mar 27
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 459d4ee Add wxUxThemeHandle instead of HTHEME


View it on GitHub or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/4ca42927fb0f2bbc478f0b5a68d5ba0c19ecb8f9/after/459d4ee8b886bda3080fe6d7851a755b4cb63b8e@github.com>

Mohmed abdel-fattah

unread,
Mar 27, 2026, 7:01:55 PM (7 days ago) Mar 27
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • b9d5717 Add static Enumes Fix lambda 32-bit MinGW builds WNDENUMPROC

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/459d4ee8b886bda3080fe6d7851a755b4cb63b8e/after/b9d5717f67863dc6fa61c2ec4f7735faf9a2d025@github.com>

Mohmed abdel-fattah

unread,
Apr 2, 2026, 6:44:01 PM (yesterday) Apr 2
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26311/before/b9d5717f67863dc6fa61c2ec4f7735faf9a2d025/after/33b871d31f220395c66af2c8be11c7d340b7ba16@github.com>

Reply all
Reply to author
Forward
0 new messages