Skip to content

Commit 57532e6

Browse files
authored
feat(controls): Add setting border color + fix background extending inside FluentWindow (#1508)
* Add setting border color * Update FluentWindow.cs * Update FluentWindow.cs It's actually SystemAccent used by Windows. * Update UnsafeNativeMethods.cs * Use border only when active, similar to WinUI * Update UnsafeNativeMethods.cs * Listen for theme changes * Ensure desktop composition is enabled before changing WindowChrome
1 parent ef47b25 commit 57532e6

File tree

2 files changed

+126
-36
lines changed

2 files changed

+126
-36
lines changed

src/Wpf.Ui/Controls/FluentWindow/FluentWindow.cs

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
// All Rights Reserved.
55

66
using System.Windows.Shell;
7+
using Wpf.Ui.Appearance;
78
using Wpf.Ui.Interop;
9+
using Wpf.Ui.Win32;
810

911
// ReSharper disable once CheckNamespace
1012
namespace Wpf.Ui.Controls;
@@ -82,6 +84,17 @@ public bool ExtendsContentIntoTitleBar
8284
public FluentWindow()
8385
{
8486
SetResourceReference(StyleProperty, typeof(FluentWindow));
87+
88+
if (Utilities.IsOSWindows11OrNewer)
89+
{
90+
ApplicationThemeManager.Changed += (_, _) =>
91+
{
92+
if (IsActive)
93+
{
94+
UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.SystemAccent);
95+
}
96+
};
97+
}
8598
}
8699

87100
/// <summary>
@@ -103,12 +116,35 @@ static FluentWindow()
103116
protected override void OnSourceInitialized(EventArgs e)
104117
{
105118
OnCornerPreferenceChanged(default, WindowCornerPreference);
106-
OnExtendsContentIntoTitleBarChanged(default, ExtendsContentIntoTitleBar);
119+
OnExtendsContentIntoTitleBarChanged(false, ExtendsContentIntoTitleBar);
107120
OnBackdropTypeChanged(default, WindowBackdropType);
108121

109122
base.OnSourceInitialized(e);
110123
}
111124

125+
/// <inheritdoc />
126+
protected override void OnActivated(EventArgs e)
127+
{
128+
base.OnActivated(e);
129+
130+
if (Utilities.IsOSWindows11OrNewer)
131+
{
132+
UnsafeNativeMethods.ApplyBorderColor(this, ApplicationAccentColorManager.SystemAccent);
133+
}
134+
}
135+
136+
/// <inheritdoc />
137+
protected override void OnDeactivated(EventArgs e)
138+
{
139+
base.OnDeactivated(e);
140+
141+
if (Utilities.IsOSWindows11OrNewer)
142+
{
143+
// DWMWA_COLOR_DEFAULT.
144+
UnsafeNativeMethods.ApplyBorderColor(this, unchecked((int)0xFFFFFFFF));
145+
}
146+
}
147+
112148
/// <summary>
113149
/// Private <see cref="WindowCornerPreference"/> property callback.
114150
/// </summary>
@@ -182,10 +218,11 @@ protected virtual void OnBackdropTypeChanged(WindowBackdropType oldValue, Window
182218
return;
183219
}
184220

221+
SetWindowChrome();
222+
185223
if (newValue == WindowBackdropType.None)
186224
{
187225
_ = WindowBackdrop.RemoveBackdrop(this);
188-
189226
return;
190227
}
191228

@@ -233,20 +270,36 @@ protected virtual void OnExtendsContentIntoTitleBarChanged(bool oldValue, bool n
233270
// AllowsTransparency = true;
234271
SetCurrentValue(WindowStyleProperty, WindowStyle.SingleBorderWindow);
235272

236-
WindowChrome.SetWindowChrome(
237-
this,
238-
new WindowChrome
239-
{
240-
CaptionHeight = 0,
241-
CornerRadius = default,
242-
GlassFrameThickness = new Thickness(-1),
243-
ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
244-
UseAeroCaptionButtons = false,
245-
}
246-
);
247-
248273
// WindowStyleProperty.OverrideMetadata(typeof(FluentWindow), new FrameworkPropertyMetadata(WindowStyle.SingleBorderWindow));
249274
// AllowsTransparencyProperty.OverrideMetadata(typeof(FluentWindow), new FrameworkPropertyMetadata(false));
250275
_ = UnsafeNativeMethods.RemoveWindowTitlebarContents(this);
251276
}
277+
278+
/// <summary>
279+
/// This virtual method is called when <see cref="WindowBackdropType"/> is changed.
280+
/// </summary>
281+
protected virtual void SetWindowChrome()
282+
{
283+
try
284+
{
285+
if (Utilities.IsCompositionEnabled)
286+
{
287+
WindowChrome.SetWindowChrome(
288+
this,
289+
new WindowChrome
290+
{
291+
CaptionHeight = 0,
292+
CornerRadius = default,
293+
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.
294+
ResizeBorderThickness = ResizeMode == ResizeMode.NoResize ? default : new Thickness(4),
295+
UseAeroCaptionButtons = false,
296+
}
297+
);
298+
}
299+
}
300+
catch (COMException)
301+
{
302+
// Ignored.
303+
}
304+
}
252305
}

src/Wpf.Ui/Interop/UnsafeNativeMethods.cs

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,64 @@ public static bool ApplyWindowCornerPreference(IntPtr handle, WindowCornerPrefer
4949

5050
int pvAttribute = (int)UnsafeReflection.Cast(cornerPreference);
5151

52-
// TODO: Validate HRESULT
53-
_ = Dwmapi.DwmSetWindowAttribute(
52+
return Dwmapi.DwmSetWindowAttribute(
5453
handle,
5554
Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE,
5655
ref pvAttribute,
5756
Marshal.SizeOf(typeof(int))
58-
);
57+
) == 0;
58+
}
5959

60-
return true;
60+
/// <summary>
61+
/// Tries to apply the color of the border.
62+
/// </summary>
63+
/// <param name="window">The window.</param>
64+
/// <param name="color">The color.</param>
65+
/// <returns><see langword="true" /> if invocation of native Windows function succeeds.</returns>
66+
public static bool ApplyBorderColor(Window window, Color color) =>
67+
GetHandle(window, out IntPtr windowHandle)
68+
&& ApplyBorderColor(windowHandle, color);
69+
70+
/// <summary>
71+
/// Tries to apply the color of the border.
72+
/// </summary>
73+
/// <param name="window">The window.</param>
74+
/// <param name="color">The color.</param>
75+
/// <returns><see langword="true" /> if invocation of native Windows function succeeds.</returns>
76+
public static bool ApplyBorderColor(Window window, int color) =>
77+
GetHandle(window, out IntPtr windowHandle)
78+
&& ApplyBorderColor(windowHandle, color);
79+
80+
/// <summary>
81+
/// Tries to apply the color of the border.
82+
/// </summary>
83+
/// <param name="handle">The handle.</param>
84+
/// <param name="color">The color.</param>
85+
/// <returns><see langword="true"/> if invocation of native Windows function succeeds.</returns>
86+
public static bool ApplyBorderColor(IntPtr handle, Color color)
87+
{
88+
return ApplyBorderColor(handle, (color.B << 16) | (color.G << 8) | color.R);
89+
}
90+
91+
/// <summary>
92+
/// Tries to apply the color of the border.
93+
/// </summary>
94+
/// <param name="handle">The handle.</param>
95+
/// <param name="color">The color.</param>
96+
/// <returns><see langword="true"/> if invocation of native Windows function succeeds.</returns>
97+
public static bool ApplyBorderColor(IntPtr handle, int color)
98+
{
99+
if (handle == IntPtr.Zero)
100+
{
101+
return false;
102+
}
103+
104+
if (!User32.IsWindow(handle))
105+
{
106+
return false;
107+
}
108+
109+
return Dwmapi.DwmSetWindowAttribute(handle, Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_BORDER_COLOR, ref color, sizeof(int)) == 0;
61110
}
62111

63112
/// <summary>
@@ -93,10 +142,7 @@ public static bool RemoveWindowDarkMode(IntPtr handle)
93142
dwAttribute = Dwmapi.DWMWINDOWATTRIBUTE.DMWA_USE_IMMERSIVE_DARK_MODE_OLD;
94143
}
95144

96-
// TODO: Validate HRESULT
97-
_ = Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int)));
98-
99-
return true;
145+
return Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))) == 0;
100146
}
101147

102148
/// <summary>
@@ -132,10 +178,7 @@ public static bool ApplyWindowDarkMode(IntPtr handle)
132178
dwAttribute = Dwmapi.DWMWINDOWATTRIBUTE.DMWA_USE_IMMERSIVE_DARK_MODE_OLD;
133179
}
134180

135-
// TODO: Validate HRESULT
136-
_ = Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int)));
137-
138-
return true;
181+
return Dwmapi.DwmSetWindowAttribute(handle, dwAttribute, ref pvAttribute, Marshal.SizeOf(typeof(int))) == 0;
139182
}
140183

141184
/// <summary>
@@ -214,15 +257,12 @@ public static bool ApplyWindowBackdrop(IntPtr handle, WindowBackdropType backgro
214257
return false;
215258
}
216259

217-
// TODO: Validate HRESULT
218-
_ = Dwmapi.DwmSetWindowAttribute(
260+
return Dwmapi.DwmSetWindowAttribute(
219261
handle,
220262
Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
221263
ref backdropPvAttribute,
222264
Marshal.SizeOf(typeof(int))
223-
);
224-
225-
return true;
265+
) == 0;
226266
}
227267

228268
/// <summary>
@@ -296,15 +336,12 @@ public static bool ApplyWindowLegacyMicaEffect(IntPtr handle)
296336
{
297337
var backdropPvAttribute = 0x1; // Enable
298338

299-
// TODO: Validate HRESULT
300-
_ = Dwmapi.DwmSetWindowAttribute(
339+
return Dwmapi.DwmSetWindowAttribute(
301340
handle,
302341
Dwmapi.DWMWINDOWATTRIBUTE.DWMWA_MICA_EFFECT,
303342
ref backdropPvAttribute,
304343
Marshal.SizeOf(typeof(int))
305-
);
306-
307-
return true;
344+
) == 0;
308345
}
309346

310347
/// <summary>

0 commit comments

Comments
 (0)