Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions src/System.Windows.Forms/System/Windows/Forms/Form.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4965,6 +4965,14 @@ private 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)
{
SetFormImmersiveDarkModeInternal(Application.IsDarkModeEnabled);
}

if (Properties.TryGetValue(s_propFormBorderColor, out Color? formBorderColor))
{
SetFormAttributeColorInternal(DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, formBorderColor.Value);
Expand All @@ -4987,6 +4995,16 @@ private void SetFormTitleProperties()
}
}

private unsafe void SetFormImmersiveDarkModeInternal(bool isDarkMode)
{
BOOL isDark = isDarkMode;
PInvoke.DwmSetWindowAttribute(
HWND,
DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE,
&isDark,
(uint)sizeof(BOOL));
}

/// <summary>
/// Removes a form from the list of owned forms. Also sets the owner of the
/// removed form to null.
Expand Down
119 changes: 119 additions & 0 deletions src/test/unit/System.Windows.Forms/System/Windows/Forms/FormTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.ComponentModel;
using System.Drawing;
using Moq;
using Windows.Win32.Graphics.Dwm;

namespace System.Windows.Forms.Tests;

Expand Down Expand Up @@ -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<object[]> Visible_Set_TestData()
{
foreach (DialogResult dialogResult in Enum.GetValues(typeof(DialogResult)))
Expand Down