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;