diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 590fbdc538..d66ef8f68d 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -97,4 +97,16 @@
baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll
+
+ CP0006
+ M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)
+ baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net10.0/Avalonia.Controls.dll
+
+
+ CP0006
+ M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)
+ baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll
+ current/Avalonia/lib/net8.0/Avalonia.Controls.dll
+
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index 4c0118872b..d839689db7 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/samples/ControlCatalog/MainWindow.xaml
@@ -12,6 +12,7 @@
CanResize="{Binding CanResize}"
CanMinimize="{Binding CanMinimize}"
CanMaximize="{Binding CanMaximize}"
+ Win32Properties.WindowCornerPreference="{Binding Win32WindowCornerPreference}"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"
x:DataType="vm:MainWindowViewModel">
diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
index a782dbec23..5114dd16e9 100644
--- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
+++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
@@ -42,6 +42,19 @@
IsChecked="{Binding CanMaximize}"
IsEnabled="{Binding CanResize}" />
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
index 0d877b253f..5d14cca1ee 100644
--- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -122,7 +122,6 @@ namespace ControlCatalog.ViewModels
set { RaiseAndSetIfChanged(ref _selectedDecorationIndex, value); }
}
-
public MiniCommand AboutCommand { get; }
public MiniCommand ExitCommand { get; }
@@ -138,5 +137,14 @@ namespace ControlCatalog.ViewModels
get => _validatedDateExample;
set => RaiseAndSetIfChanged(ref _validatedDateExample, value);
}
+
+ public Win32Properties.WindowCornerPreference[] Win32WindowCornerPreferences { get; } =
+ Enum.GetValues();
+
+ public Win32Properties.WindowCornerPreference Win32WindowCornerPreference
+ {
+ get;
+ set { RaiseAndSetIfChanged(ref field, value); }
+ }
}
}
diff --git a/src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs b/src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs
index 2e363e647d..5346dee942 100644
--- a/src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs
@@ -1,9 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Metadata;
+using Avalonia.Metadata;
using Avalonia.Platform;
using static Avalonia.Controls.Win32Properties;
@@ -21,5 +16,11 @@ namespace Avalonia.Controls.Platform
/// Gets or sets a custom callback for the window's WndProc
///
public CustomWndProcHookCallback? WndProcHookCallback { get; set; }
+
+ ///
+ /// Sets a window corner preference for the window.
+ ///
+ /// The value to set.
+ public void SetWindowCornerPreference(WindowCornerPreference preference);
}
}
diff --git a/src/Avalonia.Controls/Platform/Win32Properties.cs b/src/Avalonia.Controls/Platform/Win32Properties.cs
index b81a0c9771..9cf6d07aec 100644
--- a/src/Avalonia.Controls/Platform/Win32Properties.cs
+++ b/src/Avalonia.Controls/Platform/Win32Properties.cs
@@ -17,9 +17,38 @@ namespace Avalonia.Controls
///
public static class Win32Properties
{
+ ///
+ /// Defines the NonClientHitTestResult attached property.
+ ///
+ public static readonly AttachedProperty NonClientHitTestResultProperty =
+ AvaloniaProperty.RegisterAttached(
+ "NonClientHitTestResult",
+ typeof(Win32Properties),
+ inherits: true,
+ defaultValue: Win32HitTestValue.Client);
+
+ ///
+ /// Defines the WindowCornerPreferenceProperty attached property.
+ ///
+ public static readonly AttachedProperty WindowCornerPreferenceProperty =
+ AvaloniaProperty.RegisterAttached(
+ "WindowCornerPreference",
+ typeof(Win32Properties),
+ defaultValue: WindowCornerPreference.Default,
+ validate: ValidateWindowCornerPreference);
+
public delegate (uint style, uint exStyle) CustomWindowStylesCallback(uint style, uint exStyle);
public delegate IntPtr CustomWndProcHookCallback(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled);
+ static Win32Properties()
+ {
+ WindowCornerPreferenceProperty.Changed.AddClassHandler((window, e) =>
+ {
+ if (window.PlatformImpl is IWin32OptionsTopLevelImpl toplevelImpl)
+ toplevelImpl.SetWindowCornerPreference(e.GetNewValue());
+ });
+ }
+
///
/// Adds a callback to set the window's style.
///
@@ -72,16 +101,48 @@ namespace Avalonia.Controls
}
}
- public static readonly AttachedProperty NonClientHitTestResultProperty =
- AvaloniaProperty.RegisterAttached(
- "NonClientHitTestResult",
- typeof(Win32Properties),
- inherits: true,
- defaultValue: Win32HitTestValue.Client);
+ ///
+ /// Sets the value of the NonClientHitTestResult attached property on the specified visual.
+ ///
+ /// The visual.
+ /// The value to set.
+ public static void SetNonClientHitTestResult(Visual obj, Win32HitTestValue value)
+ => obj.SetValue(NonClientHitTestResultProperty, value);
+
+ ///
+ /// Gets the value of the NonClientHitTestResult attached property on the specified visual.
+ ///
+ /// The visual.
+ /// The hit test value for .
+ public static Win32HitTestValue GetNonClientHitTestResult(Visual obj)
+ => obj.GetValue(NonClientHitTestResultProperty);
+
+ ///
+ /// Sets the value of the WindowCornerPreference attached property on the specified window.
+ ///
+ /// The window.
+ /// The value to set.
+ ///
+ /// This value is supported starting with Windows 11 Build 22000.
+ /// It is ignored on earlier Windows versions.
+ ///
+ public static void SetWindowCornerPreference(Window window, WindowCornerPreference value)
+ => window.SetValue(WindowCornerPreferenceProperty, value);
+
+ ///
+ /// Gets the value of the WindowCornerPreference attached property on the specified window.
+ ///
+ /// The window.
+ /// The window corner preference for .
+ public static WindowCornerPreference GetWindowCornerPreference(Window window)
+ => window.GetValue(WindowCornerPreferenceProperty);
- public static void SetNonClientHitTestResult(Visual obj, Win32HitTestValue value) => obj.SetValue(NonClientHitTestResultProperty, value);
- public static Win32HitTestValue GetNonClientHitTestResult(Visual obj) => obj.GetValue(NonClientHitTestResultProperty);
+ private static bool ValidateWindowCornerPreference(WindowCornerPreference preference)
+ => preference is >= WindowCornerPreference.Default and <= WindowCornerPreference.RoundSmall;
+ ///
+ /// Represents a hit test value for a visual.
+ ///
public enum Win32HitTestValue
{
Nowhere = 0,
@@ -99,5 +160,23 @@ namespace Avalonia.Controls
BottomRight = 17,
Close = 20,
}
+
+ ///
+ /// Represents the rounded corner preference for a window.
+ ///
+ public enum WindowCornerPreference
+ {
+ /// Let the system decide when to round window corners.
+ Default = 0,
+
+ /// Never round window corners.
+ DoNotRound = 1,
+
+ /// Round the corners, if appropriate.
+ Round = 2,
+
+ /// Round the corners if appropriate, with a small radius.
+ RoundSmall = 3
+ }
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index a858761a64..a39fa9c1cb 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -108,6 +108,7 @@ namespace Avalonia.Win32
internal bool _ignoreWmChar;
private WindowTransparencyLevel _transparencyLevel;
private readonly WindowTransparencyLevel _defaultTransparencyLevel;
+ private WindowCornerPreference _cornerPreference;
private const int MaxPointerHistorySize = 512;
private static readonly PooledList s_intermediatePointsPooledList = new();
@@ -1197,6 +1198,18 @@ namespace Avalonia.Win32
return margins;
}
+ private void UpdateWindowCornerPreference()
+ {
+ if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
+ return;
+
+ unsafe
+ {
+ var preference = (int)_cornerPreference;
+ DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(int));
+ }
+ }
+
private void ExtendClientArea()
{
if (!_shown)
@@ -1219,10 +1232,6 @@ namespace Avalonia.Win32
// Make sure that Windows still paints the non-client area so we get native borders and shadows.
SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_ENABLED);
-
- // On Windows 11 21H2 and later, corners are configurable.
- if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
- SetWindowCornerPreference(DwmWindowCornerPreference.DWMWCP_ROUND);
}
else
{
@@ -1231,16 +1240,7 @@ namespace Avalonia.Win32
_extendedMargins = new Thickness();
- if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
- SetWindowCornerPreference(DwmWindowCornerPreference.DWMWCP_DEFAULT);
-
SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_USEWINDOWSTYLE);
-
- unsafe
- {
- int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
- DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int));
- }
}
if (!_isClientAreaExtended)
@@ -1262,9 +1262,6 @@ namespace Avalonia.Win32
ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended);
}
- private unsafe void SetWindowCornerPreference(DwmWindowCornerPreference value)
- => DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &value, sizeof(int));
-
private unsafe void SetNCRenderingPolicy(DwmNCRenderingPolicy value)
=> DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_NCRENDERING_POLICY, &value, sizeof(int));
@@ -1615,6 +1612,12 @@ namespace Avalonia.Win32
ExtendClientArea();
}
+ public void SetWindowCornerPreference(WindowCornerPreference preference)
+ {
+ _cornerPreference = preference;
+ UpdateWindowCornerPreference();
+ }
+
///
public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended;