From fd31615298fbc8cdc623da753269a46cce78300d Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Thu, 9 Jul 2026 09:31:12 +0530
Subject: [PATCH 1/2] Added fix for the Fix_Issue_13636
Added fix for the Fix_Issue_13636
---
.../Forms/Controls/Buttons/ButtonBase.cs | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
index 26c3292e175..3e13b450cca 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
@@ -1429,5 +1429,54 @@ protected override void WndProc(ref Message m)
break;
}
}
+
+ // WM_SETTINGCHANGE is handled outside the OwnerDraw branches so that theme updates
+ // are applied regardless of the current FlatStyle or dark-mode render path.
+ if (m.MsgInternal == PInvokeCore.WM_SETTINGCHANGE && IsImmersiveColorSet(m.LParamInternal))
+ {
+ ApplyDarkModeOnDemand();
+ }
+
+ base.WndProc(ref m);
+ }
+
+ ///
+ /// Returns if the
+ /// message signals an immersive color-set change (i.e. the user toggled dark/light mode).
+ ///
+ private static unsafe bool IsImmersiveColorSet(IntPtr lParam)
+ => lParam != IntPtr.Zero
+ && new string((char*)lParam).Equals("ImmersiveColorSet", StringComparison.Ordinal);
+
+ ///
+ /// Applies the button theme for the current color mode and repaints the control.
+ ///
+ private void ApplyDarkModeOnDemand()
+ {
+ if (!IsHandleCreated)
+ {
+ return;
+ }
+
+ PInvoke.SetWindowTheme(
+ HWND,
+ Application.IsDarkModeEnabled ? $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}" : null,
+ pszSubIdList: null);
+
+ Invalidate();
+ }
+
+ ///
+ protected override void OnHandleCreated(EventArgs e)
+ {
+ base.OnHandleCreated(e);
+
+ // base.OnHandleCreated already applies SetWindowTheme for dark mode (both initial
+ // creation and handle recreation). Only call ApplyDarkModeOnDemand when in light
+ // mode to ensure a previously applied dark theme is reset on handle recreation.
+ if (!Application.IsDarkModeEnabled)
+ {
+ ApplyDarkModeOnDemand();
+ }
}
}
From 3be3b6b1dcdf597bd8638af7a322f210ad0bded4 Mon Sep 17 00:00:00 2001
From: Abinesh p <120440951+abineshPalanisamy@users.noreply.github.com>
Date: Wed, 15 Jul 2026 11:15:12 +0530
Subject: [PATCH 2/2] Updated the review correction and added unit test cases
Updated the review correction and added unit test cases
---
.../PublicAPI.Unshipped.txt | 1 +
.../System/Windows/Forms/Control.cs | 6 ++
.../Forms/Controls/Buttons/ButtonBase.cs | 55 ++----------------
.../System/Windows/Forms/ButtonBaseTests.cs | 56 +++++++++++++++++++
.../System/Windows/Forms/ControlTests.cs | 48 ++++++++++++++++
5 files changed, 117 insertions(+), 49 deletions(-)
diff --git a/src/System.Windows.Forms/PublicAPI.Unshipped.txt b/src/System.Windows.Forms/PublicAPI.Unshipped.txt
index e69de29bb2d..15deff718b6 100644
--- a/src/System.Windows.Forms/PublicAPI.Unshipped.txt
+++ b/src/System.Windows.Forms/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+override System.Windows.Forms.ButtonBase.OnSystemColorsChanged(System.EventArgs! e) -> void
\ No newline at end of file
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
index 14efc5014ff..a03f36c1f58 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs
@@ -12608,6 +12608,12 @@ protected virtual void WndProc(ref Message m)
// Left here for debugging purposes.
// string? text = m.LParamInternal == 0 ? null : new((char*)m.LParamInternal);
+ if (m.LParamInternal != 0
+ && Application.ColorModeSet
+ && new string((char*)m.LParamInternal) is "ImmersiveColorSet")
+ {
+ OnSystemColorsChanged(EventArgs.Empty);
+ }
if (action is SYSTEM_PARAMETERS_INFO_ACTION.SPI_SETNONCLIENTMETRICS && m.LParamInternal == 0)
{
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
index 3e13b450cca..a072c925d4d 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Controls/Buttons/ButtonBase.cs
@@ -1306,6 +1306,12 @@ private protected void UpdateOwnerDraw()
}
}
+ protected override void OnSystemColorsChanged(EventArgs e)
+ {
+ UpdateOwnerDraw();
+ base.OnSystemColorsChanged(e);
+ }
+
///
/// Determines whether to use compatible text rendering engine (GDI+) or not (GDI).
///
@@ -1429,54 +1435,5 @@ protected override void WndProc(ref Message m)
break;
}
}
-
- // WM_SETTINGCHANGE is handled outside the OwnerDraw branches so that theme updates
- // are applied regardless of the current FlatStyle or dark-mode render path.
- if (m.MsgInternal == PInvokeCore.WM_SETTINGCHANGE && IsImmersiveColorSet(m.LParamInternal))
- {
- ApplyDarkModeOnDemand();
- }
-
- base.WndProc(ref m);
- }
-
- ///
- /// Returns if the
- /// message signals an immersive color-set change (i.e. the user toggled dark/light mode).
- ///
- private static unsafe bool IsImmersiveColorSet(IntPtr lParam)
- => lParam != IntPtr.Zero
- && new string((char*)lParam).Equals("ImmersiveColorSet", StringComparison.Ordinal);
-
- ///
- /// Applies the button theme for the current color mode and repaints the control.
- ///
- private void ApplyDarkModeOnDemand()
- {
- if (!IsHandleCreated)
- {
- return;
- }
-
- PInvoke.SetWindowTheme(
- HWND,
- Application.IsDarkModeEnabled ? $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}" : null,
- pszSubIdList: null);
-
- Invalidate();
- }
-
- ///
- protected override void OnHandleCreated(EventArgs e)
- {
- base.OnHandleCreated(e);
-
- // base.OnHandleCreated already applies SetWindowTheme for dark mode (both initial
- // creation and handle recreation). Only call ApplyDarkModeOnDemand when in light
- // mode to ensure a previously applied dark theme is reset on handle recreation.
- if (!Application.IsDarkModeEnabled)
- {
- ApplyDarkModeOnDemand();
- }
}
}
diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonBaseTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonBaseTests.cs
index 51e37fbca43..acb051597c9 100644
--- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonBaseTests.cs
+++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ButtonBaseTests.cs
@@ -9246,6 +9246,51 @@ public void ButtonBase_WndProc_InvokeSetStateWithHandle_Success(FlatStyle flatSt
Assert.Equal(0, createdCallCount);
}
+ [WinFormsFact]
+ public static void ButtonBase_OnSystemColorsChanged_StandardFlatStyle_RestoresOwnerDraw()
+ {
+ using SubButtonBase control = new()
+ {
+ FlatStyle = FlatStyle.Standard
+ };
+
+ control.SetUserPaintAndUserMouseStyles(false);
+
+ Assert.False(control.GetUserPaintStyle());
+ Assert.False(control.GetUserMouseStyle());
+
+ control.OnSystemColorsChangedEntry(EventArgs.Empty);
+
+ Assert.True(control.GetUserPaintStyle());
+ Assert.True(control.GetUserMouseStyle());
+ }
+
+ [WinFormsFact]
+ public static void ButtonBase_OnSystemColorsChanged_SystemFlatStyle_ClassicMode_ClearsOwnerDraw()
+ {
+ RemoteExecutor.Invoke(static () =>
+ {
+#pragma warning disable WFO5001
+ Application.SetColorMode(SystemColorMode.Classic);
+#pragma warning restore WFO5001
+
+ using SubButtonBase control = new()
+ {
+ FlatStyle = FlatStyle.System
+ };
+
+ control.SetUserPaintAndUserMouseStyles(true);
+
+ Assert.True(control.GetUserPaintStyle());
+ Assert.True(control.GetUserMouseStyle());
+
+ control.OnSystemColorsChangedEntry(EventArgs.Empty);
+
+ Assert.False(control.GetUserPaintStyle());
+ Assert.False(control.GetUserMouseStyle());
+ }).Dispose();
+ }
+
private class SubButton : Button
{
public new bool GetStyle(ControlStyles flag) => base.GetStyle(flag);
@@ -9377,6 +9422,17 @@ private class SubButtonBase : ButtonBase
public new void SetStyle(ControlStyles flag, bool value) => base.SetStyle(flag, value);
public new void WndProc(ref Message m) => base.WndProc(ref m);
+
+ public void OnSystemColorsChangedEntry(EventArgs e) => OnSystemColorsChanged(e);
+
+ public bool GetUserPaintStyle() => GetStyle(ControlStyles.UserPaint);
+
+ public bool GetUserMouseStyle() => GetStyle(ControlStyles.UserMouse);
+
+ public void SetUserPaintAndUserMouseStyles(bool value)
+ {
+ SetStyle(ControlStyles.UserPaint | ControlStyles.UserMouse, value);
+ }
}
protected override ButtonBase CreateButton() => new Button();
diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.cs
index 2286bbff228..5dc4c1ac4b0 100644
--- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.cs
+++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/ControlTests.cs
@@ -1201,6 +1201,54 @@ public void Control_Region_AssignNull_ShouldBeNull()
control.Region.Should().BeNull();
}
+ [WinFormsFact]
+ public static void Control_WndProc_WM_SETTINGCHANGE_ImmersiveColorSet_RaisesSystemColorsChanged()
+ {
+ RemoteExecutor.Invoke(static () =>
+ {
+#pragma warning disable WFO5001
+ Application.SetColorMode(SystemColorMode.System);
+#pragma warning restore WFO5001
+
+ using SubSystemColorsChangedForm form = new();
+
+ int systemColorsChangedCallCount = 0;
+ form.SystemColorsChanged += (_, _) => systemColorsChangedCallCount++;
+
+ Assert.NotEqual(IntPtr.Zero, form.Handle);
+
+ form.SendWmSettingChange("ImmersiveColorSet");
+
+ Assert.Equal(1, systemColorsChangedCallCount);
+ Assert.Equal(1, form.OnSystemColorsChangedCallCount);
+ }).Dispose();
+ }
+
+ private class SubSystemColorsChangedForm : Form
+ {
+ public int OnSystemColorsChangedCallCount { get; private set; }
+
+ public unsafe void SendWmSettingChange(string settingName)
+ {
+ fixed (char* settingNamePtr = settingName)
+ {
+ Message message = Message.Create(
+ Handle,
+ (int)PInvokeCore.WM_SETTINGCHANGE,
+ wparam: IntPtr.Zero,
+ lparam: (IntPtr)settingNamePtr);
+
+ WndProc(ref message);
+ }
+ }
+
+ protected override void OnSystemColorsChanged(EventArgs e)
+ {
+ OnSystemColorsChangedCallCount++;
+ base.OnSystemColorsChanged(e);
+ }
+ }
+
private class OnCreateControlCounter : Control
{
public int OnCreateControlCount { get; set; }