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; }