From 7f94304616c5de54d284ee169238a8a2c984a28d Mon Sep 17 00:00:00 2001 From: Sathish S <125439103+Sathish-087@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:20:02 +0530 Subject: [PATCH 1/4] Added fix for this issue 12582 Add dark mode synchronization for top-level windows after handle recreation. --- src/System.Windows.Forms/System/Windows/Forms/Control.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs index 14efc5014ff..55fb53c1847 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs @@ -9304,6 +9304,13 @@ internal virtual void RecreateHandleCore() { SetState(States.Recreate, false); + //// Keep top-level dark mode window attributes in sync after handle recreation. + //// This mirrors the dark mode preparation done from SetVisibleCore. + if (GetTopLevel() && Application.ColorModeSet && DarkModeRequestState is true) + { + PrepareDarkMode(HWND, Application.IsDarkModeEnabled); + } + // Inform children their parent's handle has been created. // This means // an Exception gets thrown before/during the invocation of DestroyHandle. From b2fc457b1d77cba2dcc5e7c9f49bd8f2cd63d505 Mon Sep 17 00:00:00 2001 From: Sathish-087 Date: Tue, 30 Jun 2026 17:42:26 +0530 Subject: [PATCH 2/4] Address review changes --- .../System/Windows/Forms/Control.cs | 7 ------- .../System/Windows/Forms/Form.cs | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Control.cs b/src/System.Windows.Forms/System/Windows/Forms/Control.cs index 55fb53c1847..14efc5014ff 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Control.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Control.cs @@ -9304,13 +9304,6 @@ internal virtual void RecreateHandleCore() { SetState(States.Recreate, false); - //// Keep top-level dark mode window attributes in sync after handle recreation. - //// This mirrors the dark mode preparation done from SetVisibleCore. - if (GetTopLevel() && Application.ColorModeSet && DarkModeRequestState is true) - { - PrepareDarkMode(HWND, Application.IsDarkModeEnabled); - } - // Inform children their parent's handle has been created. // This means // an Exception gets thrown before/during the invocation of DestroyHandle. diff --git a/src/System.Windows.Forms/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/System/Windows/Forms/Form.cs index c3900d09c6b..e428a3c9733 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Form.cs @@ -4961,10 +4961,23 @@ internal override unsafe void RecreateHandleCore() GC.KeepAlive(this); } - private void SetFormTitleProperties() + private unsafe void SetFormTitleProperties() { if (TopLevel && IsHandleCreated) { + // Re-apply the dark-mode title bar flag. DWM window attributes are not preserved + // across handle recreation, so DWMWA_USE_IMMERSIVE_DARK_MODE must be restored + // here alongside the other DWM attributes. + if (Application.ColorModeSet && DarkModeRequestState is true) + { + BOOL isDark = Application.IsDarkModeEnabled; + PInvoke.DwmSetWindowAttribute( + HWND, + DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &isDark, + (uint)sizeof(BOOL)).AssertSuccess(); + } + if (Properties.TryGetValue(s_propFormBorderColor, out Color? formBorderColor)) { SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, formBorderColor.Value); From 984575903f16852ac079857d1d53c9497e1c77bd Mon Sep 17 00:00:00 2001 From: Sathish-087 Date: Tue, 14 Jul 2026 12:19:17 +0530 Subject: [PATCH 3/4] Added unit test cases --- .../System/Windows/Forms/FormTests.cs | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/src/test/unit/System.Windows.Forms/System/Windows/Forms/FormTests.cs b/src/test/unit/System.Windows.Forms/System/Windows/Forms/FormTests.cs index 26574491052..624108d3bc7 100644 --- a/src/test/unit/System.Windows.Forms/System/Windows/Forms/FormTests.cs +++ b/src/test/unit/System.Windows.Forms/System/Windows/Forms/FormTests.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Drawing; using Moq; +using Windows.Win32.Graphics.Dwm; namespace System.Windows.Forms.Tests; @@ -1886,6 +1887,124 @@ public void Form_ShowInTaskbar_SetFalse_GetReturnsExpected() Assert.Equal(expectedDialogResult, form.DialogResult); } + [WinFormsFact] + public void Form_RecreateHandle_ReappliesImmersiveDarkModeTitleBar() + { + // Regression for https://github.com/dotnet/winforms/issues/12582 + // Avoid Application.SetColorMode — can hang in NotifySystemEventsOfColorChange + // after a form has created the SystemEvents broadcast window. + + if (SystemInformation.HighContrast) + { + return; + } + + var applicationAccessor = typeof(Application).TestAccessor.Dynamic; + SystemColorMode? previousColorMode = applicationAccessor.s_colorMode; + + try + { + applicationAccessor.s_colorMode = SystemColorMode.Dark; + Assert.True(Application.ColorModeSet); + Assert.True(Application.IsDarkModeEnabled); + + using Form form = new(); + form.TestAccessor.Dynamic.DarkModeRequestState = true; + + form.Show(); + Assert.True(form.IsHandleCreated); + + Assert.True( + TryGetUseImmersiveDarkMode(form.HWND, out bool darkBefore) && darkBefore, + "Expected immersive dark mode when the form is first shown."); + + IntPtr handleBefore = form.Handle; + + form.RightToLeft = RightToLeft.Yes; + + Assert.True(form.IsHandleCreated); + Assert.NotEqual(handleBefore, form.Handle); + + Assert.True( + TryGetUseImmersiveDarkMode(form.HWND, out bool darkAfter) && darkAfter, + "Expected immersive dark mode restored after handle recreation."); + + form.Close(); + } + finally + { + applicationAccessor.s_colorMode = previousColorMode; + } + } + + [WinFormsFact] + public void Form_ShowInTaskbar_Change_ReappliesImmersiveDarkModeTitleBar() + { + // Regression for https://github.com/dotnet/winforms/issues/12992 + // Same root cause as #12582: handle recreation must re-apply + // DWMWA_USE_IMMERSIVE_DARK_MODE via Form.SetFormTitleProperties. + // Avoid Application.SetColorMode — can hang in NotifySystemEventsOfColorChange. + + if (SystemInformation.HighContrast) + { + return; + } + + var applicationAccessor = typeof(Application).TestAccessor.Dynamic; + SystemColorMode? previousColorMode = applicationAccessor.s_colorMode; + + try + { + applicationAccessor.s_colorMode = SystemColorMode.Dark; + Assert.True(Application.ColorModeSet); + Assert.True(Application.IsDarkModeEnabled); + + using Form form = new() + { + ShowInTaskbar = true, + }; + form.TestAccessor.Dynamic.DarkModeRequestState = true; + + form.Show(); + Assert.True(form.IsHandleCreated); + + Assert.True( + TryGetUseImmersiveDarkMode(form.HWND, out bool darkBefore) && darkBefore, + "Expected immersive dark mode when the form is first shown."); + + IntPtr handleBefore = form.Handle; + + // Repro path from #12992 (toggle force recreate, same as product UI). + form.ShowInTaskbar = false; + + Assert.True(form.IsHandleCreated); + Assert.NotEqual(handleBefore, form.Handle); + + Assert.True( + TryGetUseImmersiveDarkMode(form.HWND, out bool darkAfter) && darkAfter, + "Expected immersive dark mode restored after ShowInTaskbar forces handle recreation."); + + form.Close(); + } + finally + { + applicationAccessor.s_colorMode = previousColorMode; + } + } + + private static unsafe bool TryGetUseImmersiveDarkMode(HWND hwnd, out bool isDark) + { + BOOL value = false; + HRESULT hr = PInvoke.DwmGetWindowAttribute( + hwnd, + DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &value, + (uint)sizeof(BOOL)); + + isDark = value; + return hr.Succeeded; + } + public static IEnumerable Visible_Set_TestData() { foreach (DialogResult dialogResult in Enum.GetValues(typeof(DialogResult))) From 13446e98bc23b0d3d84561f51915ef5c70f6fb00 Mon Sep 17 00:00:00 2001 From: Sathish-087 Date: Thu, 16 Jul 2026 13:32:02 +0530 Subject: [PATCH 4/4] Address review comments --- .../System/Windows/Forms/Form.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/System/Windows/Forms/Form.cs index e428a3c9733..21e2a6a6360 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Form.cs @@ -4961,7 +4961,7 @@ internal override unsafe void RecreateHandleCore() GC.KeepAlive(this); } - private unsafe void SetFormTitleProperties() + private void SetFormTitleProperties() { if (TopLevel && IsHandleCreated) { @@ -4970,12 +4970,7 @@ private unsafe void SetFormTitleProperties() // here alongside the other DWM attributes. if (Application.ColorModeSet && DarkModeRequestState is true) { - BOOL isDark = Application.IsDarkModeEnabled; - PInvoke.DwmSetWindowAttribute( - HWND, - DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, - &isDark, - (uint)sizeof(BOOL)).AssertSuccess(); + SetFormImmersiveDarkModeInternal(Application.IsDarkModeEnabled); } if (Properties.TryGetValue(s_propFormBorderColor, out Color? formBorderColor)) @@ -5000,6 +4995,16 @@ private unsafe void SetFormTitleProperties() } } + private unsafe void SetFormImmersiveDarkModeInternal(bool isDarkMode) + { + BOOL isDark = isDarkMode; + PInvoke.DwmSetWindowAttribute( + HWND, + DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &isDark, + (uint)sizeof(BOOL)); + } + /// /// Removes a form from the list of owned forms. Also sets the owner of the /// removed form to null.