Skip to content

Dark Mode issues Fixes #13474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cc87c76
Automatically switch to full color mode instantly especially when use…
memoarfaa May 17, 2025
2524745
Add Default Value For DWMWINDOWATTRIBUTE Colors.
memoarfaa May 17, 2025
daa19ac
Fixes TreeView Explorer Theme proplem by remove it and keep only Dark…
memoarfaa May 17, 2025
fcdac7c
Merge branch 'dotnet:main' into DarkModeFixes
memoarfaa May 18, 2025
9140530
If GetSysColorBrush use alternative color solid brush need creation …
memoarfaa May 25, 2025
a627a55
Merge branch 'DarkModeFixes' of https://github.com/memoarfaa/winforms…
memoarfaa May 25, 2025
1729605
If the user preference changing is for the color category, we need to…
memoarfaa May 25, 2025
abc5bcf
Fix #12991 MDI MenuBar DarMod Paint
memoarfaa Jun 6, 2025
faa2ed9
Merge remote-tracking branch 'upstream/main' into DarkModeFixes
memoarfaa Jun 15, 2025
0870f86
Fix NativeMethods.txt
memoarfaa Jun 15, 2025
4804ffc
Complete theme change notification event at Application.cs
memoarfaa Jun 15, 2025
a57e70e
Remove PrepareDarkMode Method and Proper WM_DWMNCRENDERINGCHANGED
memoarfaa Jun 15, 2025
b60bbd1
Add OnThemChanged after OnSystemColorsChanged to Control.cs
memoarfaa Jun 15, 2025
a52a4a1
Remove SetFormTitleProperties from create recreate handle and add it…
memoarfaa Jun 15, 2025
01e75a7
Add proper Explorer::TreeView dark and light Classes to avoid exption…
memoarfaa Jun 15, 2025
138f2e1
Add ColorMode ComboBox for switch themes at runtime
memoarfaa Jun 15, 2025
4058185
Set base.WndProc to WM_THEMECHANGED at light Mode
memoarfaa Jun 15, 2025
86a5137
Fix build errors
memoarfaa Jun 15, 2025
64ccdab
Fix MainForm errors
memoarfaa Jun 15, 2025
a65c958
Merge branch 'dotnet:main' into DarkModeFixes
memoarfaa Jun 20, 2025
48cd7d0
Fixes #13636
memoarfaa Jun 20, 2025
1c661d1
Merge branch 'dotnet:main' into DarkModeFixes
memoarfaa Jun 20, 2025
37beccc
Merge branch 'dotnet:main' into DarkModeFixes
memoarfaa Jun 21, 2025
65203d5
Remove unneeded and forgetting Non-Client Area Handling from Wm_Create
memoarfaa Jun 21, 2025
62ba5fa
Handel ControlStyles.ApplyThemingImplicitly
memoarfaa Jun 21, 2025
faeff85
Merge branch 'DarkModeFixes' of https://github.com/memoarfaa/winforms…
memoarfaa Jun 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ namespace Windows.Win32;

internal static partial class PInvokeCore
{
/// <inheritdoc cref="GetSysColorBrush(SYS_COLOR_INDEX)"/>
private static HBRUSH s_solidBrush;
private static HBRUSH s_lastSolidBruch;

/// <summary>
/// Returns a system color brush for the given color.
/// If using an alternative color set, manages solid brush creation and cleanup to prevent GDI leaks.
/// </summary>
/// <param name="systemColor">The system color.</param>
/// <returns>An HBRUSH for the color.</returns>
public static HBRUSH GetSysColorBrush(Color systemColor)
{
#if NET9_0_OR_GREATER
Expand All @@ -20,13 +28,35 @@ public static HBRUSH GetSysColorBrush(Color systemColor)

if (useSolidBrush)
{
// We don't have a real system color, so we'll just create a solid brush.
return CreateSolidBrush(systemColor);
// Create a new solid brush for the alternative color set.
s_solidBrush = CreateSolidBrush(systemColor);

// Delete the previous solid brush to avoid GDI leaks.
if (s_lastSolidBruch != s_solidBrush)
{
if (s_lastSolidBruch != default)
{
DeleteObject(s_lastSolidBruch);
}

s_lastSolidBruch = s_solidBrush;
}

return s_solidBrush;
}

Debug.Assert(systemColor.IsSystemColor);

// Extract the COLOR value
// Clean up any previously created solid brushes to prevent GDI leaks.
if (s_solidBrush != default || s_lastSolidBruch != default)
{
DeleteObject(s_solidBrush);
DeleteObject(s_lastSolidBruch);
s_solidBrush = default;
s_lastSolidBruch = default;
}

// Return the system color brush for the specified system color.
return GetSysColorBrush((SYS_COLOR_INDEX)(ColorTranslator.ToOle(systemColor) & 0xFF));
}
}
5 changes: 5 additions & 0 deletions src/System.Windows.Forms.Primitives/src/NativeMethods.txt
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ GetKeyState
GetLocaleInfoEx
GetMapMode
GetMenu
GetMenuBarInfo
GetMenuItemCount
GetMenuItemRect
GetMenuItemInfo
GetMessage
GetMessagePos
Expand Down Expand Up @@ -224,6 +226,7 @@ GetWindow
GetWindowDisplayAffinity
GetWindowDpiAwarenessContext
GetWindowPlacement
GetWindowTheme
GetWorldTransform
GMR_*
HC_*
Expand Down Expand Up @@ -454,6 +457,7 @@ MONTHCAL_CLASS
MOUSEHOOKSTRUCT
MoveToEx
MSFTEDIT_CLASS
MSGF_*
MsgWaitForMultipleObjectsEx
NavigateDirection
NFR_*
Expand Down Expand Up @@ -488,6 +492,7 @@ NOTIFY_ICON_INFOTIP_FLAGS
NOTIFY_ICON_MESSAGE
NotifyWinEvent
OemKeyScan
OffsetRect
OLECLOSE
OLECMDEXECOPT
OLECMDF
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ public static void SetColorMode(SystemColorMode systemColorMode)
{
SystemColors.UseAlternativeColorSet = darkModeEnabled;
NotifySystemEventsOfColorChange();
PInvokeCore.EnumWindows(SendThemeChanged);
}
}

Expand Down Expand Up @@ -723,8 +724,8 @@ private static BOOL SendThemeChangedRecursive(HWND handle)
{
// First send to all children.
PInvokeCore.EnumChildWindows(handle, SendThemeChangedRecursive);

// Then send to ourself.
// Send the theme changed message to this window.
PInvokeCore.SendMessage(handle, PInvokeCore.WM_SYSCOLORCHANGE);
PInvokeCore.SendMessage(handle, PInvokeCore.WM_THEMECHANGED);

return true;
Expand Down
147 changes: 125 additions & 22 deletions src/System.Windows.Forms/System/Windows/Forms/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5851,7 +5851,8 @@ internal virtual HBRUSH InitializeDCForWmCtlColor(HDC dc, MessageId msg)
{
PInvokeCore.SetTextColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(ForeColor));
PInvokeCore.SetBkColor(dc, (COLORREF)(uint)ColorTranslator.ToWin32(BackColor));
return BackColorBrush;
// Avoid redundant caching; use the existing brush if already owned
return GetState(States.OwnCtlBrush) ? BackColorBrush : PInvokeCore.GetSysColorBrush(BackColor);
}

return (HBRUSH)PInvokeCore.GetStockObject(GET_STOCK_OBJECT_FLAGS.NULL_BRUSH);
Expand Down Expand Up @@ -8082,9 +8083,11 @@ protected virtual void OnSystemColorsChanged(EventArgs e)
for (int i = 0; i < children.Count; i++)
{
children[i].OnSystemColorsChanged(EventArgs.Empty);
OnThemChanged(children[i]);
}
}

OnThemChanged(this);
Invalidate();

((EventHandler?)Events[s_systemColorsChangedEvent])?.Invoke(this, e);
Expand Down Expand Up @@ -10391,15 +10394,11 @@ protected virtual void SetVisibleCore(bool value)
{
// We shouldn't mess with the color mode if users haven't specifically set it.
// https://github.com/dotnet/winforms/issues/12014
if (value && Application.ColorModeSet)
{
PrepareDarkMode(HWND, Application.IsDarkModeEnabled);
}

// I Don't think we should be doing this for Forms here , but it is the same as the old code but execute Form Control.
PInvoke.ShowWindow(HWND, value ? ShowParams : SHOW_WINDOW_CMD.SW_HIDE);
}
}
#pragma warning restore WFO5001
#pragma warning restore WFO5001w
else if (IsHandleCreated || (value && _parent?.Created == true))
{
// We want to mark the control as visible so that CreateControl
Expand Down Expand Up @@ -10487,17 +10486,6 @@ protected virtual void SetVisibleCore(bool value)
| (value ? SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW : SET_WINDOW_POS_FLAGS.SWP_HIDEWINDOW));
}
}

static unsafe void PrepareDarkMode(HWND hwnd, bool darkModeEnabled)
{
BOOL value = darkModeEnabled;

PInvoke.DwmSetWindowAttribute(
hwnd,
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&value,
(uint)sizeof(BOOL)).AssertSuccess();
}
}

/// <summary>
Expand Down Expand Up @@ -11098,6 +11086,67 @@ internal void WindowReleaseHandle()
_window.ReleaseHandle();
}

/// <summary>
/// Updates the visual theme of the specified control based on the application's current theme settings.
/// This method applies the appropriate theme to the specified control depending on whether the application is in dark mode or light mode.
/// </summary>
/// <param name="control">The control whose theme is to be updated. Supported controls include <see cref="ProgressBar"/>, <see
/// cref="ComboBox"/>, <see cref="ListView"/>, and others.</param>
private void OnThemChanged(Control control)
{
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (control.GetStyle(ControlStyles.ApplyThemingImplicitly))
{
switch (control)
{
case ProgressBar:
string? isDarkTheme = Application.IsDarkModeEnabled ? "" : null;
PInvoke.SetWindowTheme(HWND, isDarkTheme, isDarkTheme);
break;
case ComboBox:
{
PInvoke.SetWindowTheme(HWND,
Application.IsDarkModeEnabled
? $"{DarkModeIdentifier}_{ComboBoxButtonThemeIdentifier}"
: null, null);
COMBOBOXINFO cInfo = default;
cInfo.cbSize = (uint)sizeof(COMBOBOXINFO);
_ = PInvoke.GetComboBoxInfo(HWND, ref cInfo);
PInvoke.SetWindowTheme(cInfo.hwndList,
Application.IsDarkModeEnabled
? $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}"
: null, null);
PInvokeCore.SendMessage(cInfo.hwndList, PInvokeCore.WM_THEMECHANGED);
PInvokeCore.SendMessage(HWND, PInvokeCore.WM_THEMECHANGED);
}

break;
case ListView:
PInvoke.SetWindowTheme(HWND,
Application.IsDarkModeEnabled
? $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}"
: null, null);

// Get the ListView's ColumnHeader handle:
HWND columnHeaderHandle = (HWND)PInvokeCore.SendMessage(this, PInvoke.LVM_GETHEADER, (WPARAM)0, (LPARAM)0);
PInvoke.SetWindowTheme(columnHeaderHandle, Application.IsDarkModeEnabled
? $"{DarkModeIdentifier}_{ItemsViewThemeIdentifier}"
: null,
null);
break;
default:
// For all other controls, we set the theme to the Explorer theme.
PInvoke.SetWindowTheme(HWNDInternal,
Application.IsDarkModeEnabled
? $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}"
: null, null);
break;
}
}
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

}

private void WmClose(ref Message m)
{
if (ParentInternal is not null)
Expand Down Expand Up @@ -11786,6 +11835,39 @@ private void WmOwnerDraw(ref Message m)
DefWndProc(ref m);
}

/// <summary>
/// Handles the WM_DWMNCRENDERINGCHANGED message, which is sent when the Desktop Window Manager (DWM) non-client area rendering policy changes.
/// This message allows the control to update its appearance or behavior in response to changes in DWM rendering, such as enabling or disabling
/// dark mode for window, borders and title bar Colors.
/// </summary>
private unsafe void WmDwmNcRenderingChanged(ref Message m)
{
DefWndProc(ref m);

// 1. Check if the control's handle is created and Desktop Window Manager (DWM) non-client area rendering policy is enabled.
// 2. If so, update the non-client rendering (e.g., update window frame, invalidate non-client area).

// Specifies whether DWM rendering is enabled for the non-client area of the window. WPARAM is TRUE if enabled; otherwise, FALSE.
if (m.WParamInternal != 0)
{
bool hasDarkNcArea;
if (PInvoke.DwmGetWindowAttribute(m.HWND, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &hasDarkNcArea, sizeof(int)).Succeeded)
{
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (Application.IsDarkModeEnabled == hasDarkNcArea)
{
// No change in dark mode state, no need to update.
return;
}

hasDarkNcArea = Application.IsDarkModeEnabled;
PInvoke.DwmSetWindowAttribute(m.HWND, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, &hasDarkNcArea, sizeof(int));
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

}
}
}

/// <summary>
/// Handles the WM_PAINT messages. This should only be called for userpaint controls.
/// </summary>
Expand Down Expand Up @@ -12517,7 +12599,9 @@ protected virtual void WndProc(ref Message m)
case MessageId.WM_REFLECT_NOTIFYFORMAT:
m.ResultInternal = (LRESULT)(nint)PInvoke.NFR_UNICODE;
break;

case PInvokeCore.WM_DWMNCRENDERINGCHANGED:
WmDwmNcRenderingChanged(ref m);
break;
case PInvokeCore.WM_SHOWWINDOW:
WmShowWindow(ref m);
break;
Expand Down Expand Up @@ -12564,8 +12648,28 @@ protected virtual void WndProc(ref Message m)
break;

case PInvokeCore.WM_SETTINGCHANGE:
if (GetExtendedState(ExtendedStates.InterestedInUserPreferenceChanged) && GetTopLevel())
{
if (GetExtendedState(ExtendedStates.InterestedInUserPreferenceChanged))
{
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Application.SetColorMode(Application.ColorMode);

const string DarkModeThemeIdentifier = $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}";
PInvoke.SetWindowTheme(m.HWND,
Application.IsDarkModeEnabled
? DarkModeThemeIdentifier
: default,
null);
PInvoke.RedrawWindow(
m.HWND,
lprcUpdate: (RECT*)null,
HRGN.Null,
REDRAW_WINDOW_FLAGS.RDW_INVALIDATE
| REDRAW_WINDOW_FLAGS.RDW_FRAME
| REDRAW_WINDOW_FLAGS.RDW_ERASE
| REDRAW_WINDOW_FLAGS.RDW_ALLCHILDREN);
if (!GetTopLevel())
return;
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
SYSTEM_PARAMETERS_INFO_ACTION action = (SYSTEM_PARAMETERS_INFO_ACTION)(uint)m.WParamInternal;

// Left here for debugging purposes.
Expand Down Expand Up @@ -12612,7 +12716,6 @@ protected virtual void WndProc(ref Message m)
}

break;

case PInvokeCore.WM_SYSCOLORCHANGE:
if (GetExtendedState(ExtendedStates.InterestedInUserPreferenceChanged) && GetTopLevel())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,25 @@ private void ResetUseVisualStyleBackColor()
Invalidate();
}

private void WmSysColorChange(ref Message m)
{
base.WndProc(ref m);
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

// If disabled DarkMode, we need to update the button's appearance
// to reflect the new color scheme.
if (FlatStyle == FlatStyle.Standard && !Application.IsDarkModeEnabled)
{
if (OwnerDraw != GetStyle(ControlStyles.UserPaint))
{
SetStyle(ControlStyles.UserMouse | ControlStyles.UserPaint, OwnerDraw);
_cachedAdapterType = default;
}
}
#pragma warning restore WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

}

private bool ShouldSerializeUseVisualStyleBackColor() => _isEnableVisualStyleBackgroundSet;

protected override void WndProc(ref Message m)
Expand All @@ -1366,6 +1385,9 @@ protected override void WndProc(ref Message m)
}

return;
case PInvokeCore.WM_SYSCOLORCHANGE:
WmSysColorChange(ref m);
break;
}

if (OwnerDraw)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ protected override CreateParams CreateParams
{
get
{
#pragma warning disable WFO5001
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
#pragma warning restore WFO5001

CreateParams cp = base.CreateParams;
cp.ClassName = PInvoke.WC_COMBOBOX;
cp.Style |= (int)WINDOW_STYLE.WS_VSCROLL | PInvoke.CBS_HASSTRINGS | PInvoke.CBS_AUTOHSCROLL;
Expand Down Expand Up @@ -2335,7 +2339,7 @@ protected override unsafe void OnHandleCreated(EventArgs e)
}

#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
if (Application.IsDarkModeEnabled)
if (Application.IsDarkModeEnabled &&GetStyle(ControlStyles.ApplyThemingImplicitly))
{
// Style the ComboBox Open-Button:
PInvoke.SetWindowTheme(HWND, $"{DarkModeIdentifier}_{ComboBoxButtonThemeIdentifier}", null);
Expand Down
Loading