From 32467e190930eb095a25883d2d07ed56e589b95e Mon Sep 17 00:00:00 2001 From: Nuklon Date: Fri, 8 Aug 2025 19:28:40 +0200 Subject: [PATCH 1/8] Add setting border color --- src/Wpf.Ui/Interop/UnsafeNativeMethods.cs | 61 +++++++++++++++-------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs index be8c6f217..46371f3e3 100644 --- a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs +++ b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs @@ -49,15 +49,44 @@ public static bool ApplyWindowCornerPreference(IntPtr handle, WindowCornerPrefer int pvAttribute = (int)UnsafeReflection.Cast(cornerPreference); - // TODO: Validate HRESULT - _ = Dwmapi.DwmSetWindowAttribute( + return Dwmapi.DwmSetWindowAttribute( handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, ref pvAttribute, Marshal.SizeOf(typeof(int)) - ); + ) == 0; + } - return true; + /// + /// Tries to apply the color of the border. + /// + /// The window. + /// The color. + /// if invocation of native Windows function succeeds. + public static bool ApplyBorderColor(Window window, Color color) => + GetHandle(window, out IntPtr windowHandle) + && ApplyBorderColor(windowHandle, color); + + /// + /// Tries to apply the color of the border. + /// + /// The handle. + /// The color. + /// if invocation of native Windows function succeeds. + public static bool ApplyBorderColor(IntPtr handle, Color color) + { + if (handle == IntPtr.Zero) + { + return false; + } + + if (!User32.IsWindow(handle)) + { + return false; + } + + int colorNum = (color.B << 16) | (color.G << 8) | color.R; + return Dwmapi.DwmSetWindowAttribute(handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, ref colorNum, sizeof(int)) == 0; } /// @@ -93,10 +122,7 @@ public static bool RemoveWindowDarkMode(IntPtr handle) dwAttribute = Dwmapi.DWMWINDOWATTRIBUTE.DMWA_USE_IMMERSIVE_DARK_MODE_OLD; } - // TODO: Validate HRESULT - _ = Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))); - - return true; + return Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))) == 0; } /// @@ -132,10 +158,7 @@ public static bool ApplyWindowDarkMode(IntPtr handle) dwAttribute = Dwmapi.DWMWINDOWATTRIBUTE.DMWA_USE_IMMERSIVE_DARK_MODE_OLD; } - // TODO: Validate HRESULT - _ = Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))); - - return true; + return Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))) == 0; } /// @@ -214,15 +237,12 @@ public static bool ApplyWindowBackdrop(IntPtr handle, WindowBackdropType backgro return false; } - // TODO: Validate HRESULT - _ = Dwmapi.DwmSetWindowAttribute( + return Dwmapi.DwmSetWindowAttribute( handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, ref backdropPvAttribute, Marshal.SizeOf(typeof(int)) - ); - - return true; + ) == 0; } /// @@ -296,15 +316,12 @@ public static bool ApplyWindowLegacyMicaEffect(IntPtr handle) { var backdropPvAttribute = 0x1; // Enable - // TODO: Validate HRESULT - _ = Dwmapi.DwmSetWindowAttribute( + return Dwmapi.DwmSetWindowAttribute( handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_MICA_EFFECT, ref backdropPvAttribute, Marshal.SizeOf(typeof(int)) - ); - - return true; + ) == 0; } /// From 957eb0a2a0f641b1d330ff14582fd4278dcbfa9b Mon Sep 17 00:00:00 2001 From: Nuklon Date: Fri, 8 Aug 2025 19:32:39 +0200 Subject: [PATCH 2/8] Update FluentWindow.cs --- src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index bccafdb30..363597508 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -4,7 +4,9 @@ // All Rights Reserved. using System.Windows.Shell; +using Wpf.Ui.Appearance; using Wpf.Ui.Interop; +using Wpf.Ui.Win32; // ReSharper disable once CheckNamespace namespace Wpf.Ui.Controls; @@ -106,6 +108,11 @@ protected override void OnSourceInitialized(EventArgs e) OnExtendsContentIntoTitleBarChanged(default, ExtendsContentIntoTitleBar); OnBackdropTypeChanged(default, WindowBackdropType); + if (Utilities.IsOSWindows11OrNewer) + { + UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.PrimaryAccent); + } + base.OnSourceInitialized(e); } From b697a68c79fd16f7a46cb414b3dd10c19150cec9 Mon Sep 17 00:00:00 2001 From: Nuklon Date: Fri, 8 Aug 2025 19:38:24 +0200 Subject: [PATCH 3/8] Update FluentWindow.cs It's actually SystemAccent used by Windows. --- src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index 363597508..86bf68c30 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -110,7 +110,7 @@ protected override void OnSourceInitialized(EventArgs e) if (Utilities.IsOSWindows11OrNewer) { - UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.PrimaryAccent); + UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.SystemAccent); } base.OnSourceInitialized(e); From 22b84f7feac2e510c8db6d9ef60980f6c4569aec Mon Sep 17 00:00:00 2001 From: Nuklon Date: Fri, 8 Aug 2025 19:45:48 +0200 Subject: [PATCH 4/8] Update UnsafeNativeMethods.cs --- src/Wpf.Ui/Interop/UnsafeNativeMethods.cs | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs index 46371f3e3..5c497c655 100644 --- a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs +++ b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs @@ -67,6 +67,16 @@ public static bool ApplyBorderColor(Window window, Color color) => GetHandle(window, out IntPtr windowHandle) && ApplyBorderColor(windowHandle, color); + /// + /// Tries to apply the color of the border. + /// + /// The window. + /// The color. + /// if invocation of native Windows function succeeds. + public static bool ApplyBorderColor(Window window, int color) => + GetHandle(window, out IntPtr windowHandle) + && ApplyBorderColor(windowHandle, color); + /// /// Tries to apply the color of the border. /// @@ -74,6 +84,18 @@ public static bool ApplyBorderColor(Window window, Color color) => /// The color. /// if invocation of native Windows function succeeds. public static bool ApplyBorderColor(IntPtr handle, Color color) + { + int colorNum = (color.B << 16) | (color.G << 8) | color.R; + return ApplyBorderColor(handle, colorNum); + } + + /// + /// Tries to apply the color of the border. + /// + /// The handle. + /// The color. + /// if invocation of native Windows function succeeds. + public static bool ApplyBorderColor(IntPtr handle, int color) { if (handle == IntPtr.Zero) { @@ -85,8 +107,7 @@ public static bool ApplyBorderColor(IntPtr handle, Color color) return false; } - int colorNum = (color.B << 16) | (color.G << 8) | color.R; - return Dwmapi.DwmSetWindowAttribute(handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, ref colorNum, sizeof(int)) == 0; + return Dwmapi.DwmSetWindowAttribute(handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, ref color, sizeof(int)) == 0; } /// From 52cf5baa1abd8796963dd902a53ee465f1279589 Mon Sep 17 00:00:00 2001 From: Nuklon Date: Fri, 8 Aug 2025 19:46:16 +0200 Subject: [PATCH 5/8] Use border only when active, similar to WinUI --- .../Controls/FluentWindow/FluentWindow.cs | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index 86bf68c30..d3a5c6ed2 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -105,15 +105,33 @@ static FluentWindow() protected override void OnSourceInitialized(EventArgs e) { OnCornerPreferenceChanged(default, WindowCornerPreference); - OnExtendsContentIntoTitleBarChanged(default, ExtendsContentIntoTitleBar); + OnExtendsContentIntoTitleBarChanged(false, ExtendsContentIntoTitleBar); OnBackdropTypeChanged(default, WindowBackdropType); + base.OnSourceInitialized(e); + } + + /// + protected override void OnActivated(EventArgs e) + { + base.OnActivated(e); + if (Utilities.IsOSWindows11OrNewer) { UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.SystemAccent); } + } - base.OnSourceInitialized(e); + /// + protected override void OnDeactivated(EventArgs e) + { + base.OnDeactivated(e); + + if (Utilities.IsOSWindows11OrNewer) + { + // DWMWA_COLOR_DEFAULT. + UnsafeNativeMethods.ApplyBorderColor(this, unchecked((int)0xFFFFFFFF)); + } } /// @@ -189,10 +207,11 @@ protected virtual void OnBackdropTypeChanged(WindowBackdropType oldValue, Window return; } + SetWindowChrome(); + if (newValue == WindowBackdropType.None) { _ = WindowBackdrop.RemoveBackdrop(this); - return; } @@ -240,20 +259,26 @@ protected virtual void OnExtendsContentIntoTitleBarChanged(bool oldValue, bool n // AllowsTransparency = true; SetCurrentValue(WindowStyleProperty, WindowStyle.SingleBorderWindow); - WindowChrome.SetWindowChrome( - this, - new WindowChrome - { - CaptionHeight = 0, - CornerRadius = default, - GlassFrameThickness = new Thickness(-1), - ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4), - UseAeroCaptionButtons = false, - } - ); - // WindowStyleProperty.OverrideMetadata(typeof(FluentWindow), new FrameworkPropertyMetadata(WindowStyle.SingleBorderWindow)); // AllowsTransparencyProperty.OverrideMetadata(typeof(FluentWindow), new FrameworkPropertyMetadata(false)); _ = UnsafeNativeMethods.RemoveWindowTitlebarContents(this); } + + /// + /// This virtual method is called when is changed. + /// + protected virtual void SetWindowChrome() + { + WindowChrome.SetWindowChrome( + this, + new WindowChrome + { + CaptionHeight = 0, + CornerRadius = default, + GlassFrameThickness = WindowBackdropType == WindowBackdropType.None ? new Thickness(0.00001) : new Thickness(-1), // 0.00001 so there's no glass frame drawn around the window, but the border is still drawn. + ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4), + UseAeroCaptionButtons = false, + } + ); + } } From b9bb8cfe10eda11b368f215eb003903e8d6b5e36 Mon Sep 17 00:00:00 2001 From: Nuklon Date: Mon, 11 Aug 2025 13:25:30 +0200 Subject: [PATCH 6/8] Update UnsafeNativeMethods.cs --- src/Wpf.Ui/Interop/UnsafeNativeMethods.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs index 5c497c655..d12877871 100644 --- a/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs +++ b/src/Wpf.Ui/Interop/UnsafeNativeMethods.cs @@ -85,8 +85,7 @@ public static bool ApplyBorderColor(Window window, int color) => /// if invocation of native Windows function succeeds. public static bool ApplyBorderColor(IntPtr handle, Color color) { - int colorNum = (color.B << 16) | (color.G << 8) | color.R; - return ApplyBorderColor(handle, colorNum); + return ApplyBorderColor(handle, (color.B << 16) | (color.G << 8) | color.R); } /// From e1a5f3474db9ee8a7b908537df249f91a51bea9d Mon Sep 17 00:00:00 2001 From: Nuklon Date: Thu, 14 Aug 2025 13:01:59 +0200 Subject: [PATCH 7/8] Listen for theme changes --- src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index d3a5c6ed2..eb53174c5 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -84,6 +84,17 @@ public bool ExtendsContentIntoTitleBar public FluentWindow() { SetResourceReference(StyleProperty, typeof(FluentWindow)); + + if (Utilities.IsOSWindows11OrNewer) + { + ApplicationThemeManager.Changed += (_, _) => + { + if (IsActive) + { + UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.SystemAccent); + } + }; + } } /// From 90f92311d1b7ea746d26f913ac34b9c7f81e29c5 Mon Sep 17 00:00:00 2001 From: Nuklon Date: Sun, 28 Sep 2025 16:35:23 +0200 Subject: [PATCH 8/8] Ensure desktop composition is enabled before changing WindowChrome --- .../Controls/FluentWindow/FluentWindow.cs | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs index eb53174c5..d019c38a9 100644 --- a/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs +++ b/src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs @@ -280,16 +280,26 @@ protected virtual void OnExtendsContentIntoTitleBarChanged(bool oldValue, bool n /// protected virtual void SetWindowChrome() { - WindowChrome.SetWindowChrome( - this, - new WindowChrome - { - CaptionHeight = 0, - CornerRadius = default, - GlassFrameThickness = WindowBackdropType == WindowBackdropType.None ? new Thickness(0.00001) : new Thickness(-1), // 0.00001 so there's no glass frame drawn around the window, but the border is still drawn. - ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4), - UseAeroCaptionButtons = false, - } - ); + try + { + if (Utilities.IsCompositionEnabled) + { + WindowChrome.SetWindowChrome( + this, + new WindowChrome + { + CaptionHeight = 0, + CornerRadius = default, + GlassFrameThickness = WindowBackdropType == WindowBackdropType.None ? new Thickness(0.00001) : new Thickness(-1), // 0.00001 so there's no glass frame drawn around the window, but the border is still drawn. + ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4), + UseAeroCaptionButtons = false, + } + ); + } + } + catch (COMException) + { + // Ignored. + } } }