diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ListView/ListView.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ListView/ListView.cs index 24a6688e4d7..630db12779d 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ListView/ListView.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/ListView/ListView.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Windows.Forms.Layout; using System.Windows.Forms.VisualStyles; +using Windows.Win32.Graphics.Dwm; using Windows.Win32.System.Variant; using Windows.Win32.UI.Accessibility; using Windows.Win32.UI.Input.KeyboardAndMouse; @@ -4512,7 +4513,7 @@ protected override void OnFontChanged(EventArgs e) InvalidateColumnHeaders(); } - protected override void OnHandleCreated(EventArgs e) + protected override unsafe void OnHandleCreated(EventArgs e) { // don't persist flipViewToLargeIconAndSmallIcon across handle recreations... FlipViewToLargeIconAndSmallIcon = false; @@ -4533,6 +4534,21 @@ protected override void OnHandleCreated(EventArgs e) // Get the ListView's ColumnHeader handle: HWND columnHeaderHandle = (HWND)PInvokeCore.SendMessage(this, PInvoke.LVM_GETHEADER, (WPARAM)0, (LPARAM)0); PInvoke.SetWindowTheme(columnHeaderHandle, $"{DarkModeIdentifier}_{ItemsViewThemeIdentifier}", null); + + // Get the ListView's ToolTip handle: + HWND toolTipHandle = (HWND)PInvokeCore.SendMessage(this, PInvoke.LVM_GETTOOLTIPS, (WPARAM)0, (LPARAM)0); + PInvoke.SetWindowTheme(toolTipHandle, $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}", null); + + // Round the corners of the ToolTip window. + if (OsVersion.IsWindows11_OrGreater()) + { + DWM_WINDOW_CORNER_PREFERENCE roundSmall = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL; + PInvoke.DwmSetWindowAttribute( + toolTipHandle, + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &roundSmall, + sizeof(DWM_WINDOW_CORNER_PREFERENCE)); + } } #pragma warning restore WFO5001 diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TabControl/TabControl.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TabControl/TabControl.cs index 618ce40bb14..a6329bfe012 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TabControl/TabControl.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TabControl/TabControl.cs @@ -7,6 +7,7 @@ using System.Drawing.Design; using System.Text; using System.Windows.Forms.Layout; +using Windows.Win32.Graphics.Dwm; using Windows.Win32.UI.Accessibility; namespace System.Windows.Forms; @@ -1221,7 +1222,7 @@ protected override void OnGotFocus(EventArgs e) /// We do some work here to configure the handle. /// Overriders should call base.OnHandleCreated() /// - protected override void OnHandleCreated(EventArgs e) + protected override unsafe void OnHandleCreated(EventArgs e) { if (!IsHandleCreated) { @@ -1259,6 +1260,24 @@ protected override void OnHandleCreated(EventArgs e) HWND.HWND_TOPMOST, 0, 0, 0, 0, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + +#pragma warning disable WFO5001 + if (Application.IsDarkModeEnabled) + { + PInvoke.SetWindowTheme(tooltipHwnd, $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}", null); + + // Round the corners of the ToolTip window. + if (OsVersion.IsWindows11_OrGreater()) + { + DWM_WINDOW_CORNER_PREFERENCE roundSmall = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL; + PInvoke.DwmSetWindowAttribute( + tooltipHwnd, + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &roundSmall, + sizeof(DWM_WINDOW_CORNER_PREFERENCE)); + } + } +#pragma warning restore WFO5001 } } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs index a7e66b35eb7..709073ed50f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Controls/TreeView/TreeView.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Windows.Forms.Layout; using System.Windows.Forms.VisualStyles; +using Windows.Win32.Graphics.Dwm; using Windows.Win32.System.Variant; using Windows.Win32.UI.Accessibility; using static System.Windows.Forms.TreeNode; @@ -1839,7 +1840,7 @@ protected override bool IsInputKey(Keys keyData) protected virtual void OnDrawNode(DrawTreeNodeEventArgs e) => _onDrawNode?.Invoke(this, e); - protected override void OnHandleCreated(EventArgs e) + protected override unsafe void OnHandleCreated(EventArgs e) { if (!IsHandleCreated) { @@ -1893,6 +1894,24 @@ protected override void OnHandleCreated(EventArgs e) { PInvokeCore.SendMessage(this, PInvoke.TVM_SETTEXTCOLOR, 0, c.ToWin32()); } + + if (Application.IsDarkModeEnabled) + { + // Get the TreeView's ToolTip handle: + HWND toolTipHandle = (HWND)PInvokeCore.SendMessage(HWND, PInvoke.TVM_GETTOOLTIPS, (WPARAM)0, (LPARAM)0); + PInvoke.SetWindowTheme(toolTipHandle, $"{DarkModeIdentifier}_{ExplorerThemeIdentifier}", null); + + // Round the corners of the ToolTip window. + if (OsVersion.IsWindows11_OrGreater()) + { + DWM_WINDOW_CORNER_PREFERENCE roundSmall = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL; + PInvoke.DwmSetWindowAttribute( + toolTipHandle, + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &roundSmall, + sizeof(DWM_WINDOW_CORNER_PREFERENCE)); + } + } #pragma warning restore WFO5001 // Put the LineColor into the native control only if set. diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ToolTip/ToolTip.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ToolTip/ToolTip.cs index 6fad5f45a92..efcfa1c6c4f 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ToolTip/ToolTip.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ToolTip/ToolTip.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Drawing; +using Windows.Win32.Graphics.Dwm; namespace System.Windows.Forms; @@ -714,10 +715,30 @@ private unsafe void CreateHandle() _window.CreateHandle(cp); +#pragma warning disable WFO5001 if (SystemInformation.HighContrast) { PInvoke.SetWindowTheme(HWND, string.Empty, string.Empty); } + else if (Application.IsDarkModeEnabled) + { + if (!_isBalloon && OsVersion.IsWindows11_OrGreater()) + { + DWM_WINDOW_CORNER_PREFERENCE roundSmall = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL; + PInvoke.DwmSetWindowAttribute( + HWND, + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &roundSmall, + sizeof(DWM_WINDOW_CORNER_PREFERENCE)); + } + + PInvokeCore.SendMessage( + HWND, + PInvoke.TTM_SETWINDOWTHEME, + default, + $"{Control.DarkModeIdentifier}_{Control.ExplorerThemeIdentifier}"); + } +#pragma warning restore WFO5001 } // If in OwnerDraw mode, we don't want the default border. diff --git a/src/System.Windows.Forms/tests/TestUtilities/ApplicationColorModeScope.cs b/src/System.Windows.Forms/tests/TestUtilities/ApplicationColorModeScope.cs new file mode 100644 index 00000000000..47b8eed443d --- /dev/null +++ b/src/System.Windows.Forms/tests/TestUtilities/ApplicationColorModeScope.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Forms; + +namespace System; + +#pragma warning disable WFO5001 +/// +/// Scope for setting the default color mode (dark mode) for the application. Use in a statement. +/// +public readonly ref struct ApplicationColorModeScope +{ + private readonly SystemColorMode _originalColorMode; + + public ApplicationColorModeScope(SystemColorMode colorMode) + { + _originalColorMode = Application.ColorMode; + + if (_originalColorMode != colorMode) + { + Application.SetColorMode(colorMode); + } + } + + public void Dispose() + { + if (Application.ColorMode != _originalColorMode) + { + Application.SetColorMode(_originalColorMode); + } + } +} +#pragma warning restore WFO5001 diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolTipTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolTipTests.cs index bbfd4f46ae7..f93e3786d55 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolTipTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ToolTipTests.cs @@ -6,6 +6,7 @@ using System.Windows.Forms.Automation; using Moq; using Moq.Protected; +using Windows.Win32.Graphics.Dwm; namespace System.Windows.Forms.Tests; @@ -782,6 +783,44 @@ public void ToolTip_ToString_Invoke_ReturnsExpected() Assert.Equal("System.Windows.Forms.ToolTip InitialDelay: 500, ShowAlways: False", toolTip.ToString()); } +#pragma warning disable WFO5001 + [WinFormsTheory] + [BoolData] + public unsafe void ToolTip_DarkMode_GetCornerPreference_ReturnsExpected(bool value) + { + if (!OsVersion.IsWindows11_OrGreater()) + { + return; + } + + if (SystemInformation.HighContrast) + { + // We don't run this test in HighContrast mode. + return; + } + + using ApplicationColorModeScope colorModeScope = new(colorMode: SystemColorMode.Dark); + using SubToolTip toolTip = new() + { + IsBalloon = value, + }; + + toolTip.Handle.Should().NotBe(IntPtr.Zero); // A workaround to create the toolTip native window Handle + + DWM_WINDOW_CORNER_PREFERENCE cornerPreference; + PInvoke.DwmGetWindowAttribute( + toolTip.HWND, + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &cornerPreference, + sizeof(DWM_WINDOW_CORNER_PREFERENCE)); + + cornerPreference.Should().Be( + toolTip.IsBalloon + ? DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DEFAULT + : DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL); + } +#pragma warning restore WFO5001 + [WinFormsFact] public void ToolTip_SetToolTipToControl_Invokes_SetToolTip_OfControl() {