Browse Source

Implement Win32Properties.WindowCornerPreference (#21615)

* Implement Win32Properties.WindowCornerPreference

* Add corner preference to ControlCatalog

* Update API suppressions
pull/21624/head
Julien Lebosquain 3 months ago
committed by GitHub
parent
commit
b2c8170f95
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      api/Avalonia.nupkg.xml
  2. 1
      samples/ControlCatalog/MainWindow.xaml
  3. 13
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  4. 10
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  5. 13
      src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs
  6. 95
      src/Avalonia.Controls/Platform/Win32Properties.cs
  7. 35
      src/Windows/Avalonia.Win32/WindowImpl.cs

12
api/Avalonia.nupkg.xml

@ -97,4 +97,16 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left> <Left>baseline/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right> <Right>current/Avalonia/lib/net8.0/Avalonia.OpenGL.dll</Right>
</Suppression> </Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Controls.Platform.IWin32OptionsTopLevelImpl.SetWindowCornerPreference(Avalonia.Controls.Win32Properties.WindowCornerPreference)</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Controls.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Controls.dll</Right>
</Suppression>
</Suppressions> </Suppressions>

1
samples/ControlCatalog/MainWindow.xaml

@ -12,6 +12,7 @@
CanResize="{Binding CanResize}" CanResize="{Binding CanResize}"
CanMinimize="{Binding CanMinimize}" CanMinimize="{Binding CanMinimize}"
CanMaximize="{Binding CanMaximize}" CanMaximize="{Binding CanMaximize}"
Win32Properties.WindowCornerPreference="{Binding Win32WindowCornerPreference}"
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"
x:DataType="vm:MainWindowViewModel"> x:DataType="vm:MainWindowViewModel">
<NativeMenu.Menu> <NativeMenu.Menu>

13
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@ -42,6 +42,19 @@
IsChecked="{Binding CanMaximize}" IsChecked="{Binding CanMaximize}"
IsEnabled="{Binding CanResize}" /> IsEnabled="{Binding CanResize}" />
<DockPanel>
<Label Content="Corner Preference (Windows 11+): "
Margin="4"
DockPanel.Dock="Left" />
<ComboBox ItemsSource="{Binding Win32WindowCornerPreferences}"
SelectedItem="{Binding Win32WindowCornerPreference, Mode=TwoWay}"
HorizontalAlignment="Left"
MinWidth="220" />
</DockPanel>
</StackPanel> </StackPanel>
<StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Mobile=true}"> <StackPanel Spacing="10" Margin="25" IsEnabled="{OnFormFactor false, Mobile=true}">

10
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -122,7 +122,6 @@ namespace ControlCatalog.ViewModels
set { RaiseAndSetIfChanged(ref _selectedDecorationIndex, value); } set { RaiseAndSetIfChanged(ref _selectedDecorationIndex, value); }
} }
public MiniCommand AboutCommand { get; } public MiniCommand AboutCommand { get; }
public MiniCommand ExitCommand { get; } public MiniCommand ExitCommand { get; }
@ -138,5 +137,14 @@ namespace ControlCatalog.ViewModels
get => _validatedDateExample; get => _validatedDateExample;
set => RaiseAndSetIfChanged(ref _validatedDateExample, value); set => RaiseAndSetIfChanged(ref _validatedDateExample, value);
} }
public Win32Properties.WindowCornerPreference[] Win32WindowCornerPreferences { get; } =
Enum.GetValues<Win32Properties.WindowCornerPreference>();
public Win32Properties.WindowCornerPreference Win32WindowCornerPreference
{
get;
set { RaiseAndSetIfChanged(ref field, value); }
}
} }
} }

13
src/Avalonia.Controls/Platform/IWin32OptionsTopLevelImpl.cs

@ -1,9 +1,4 @@
using System; using Avalonia.Metadata;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using static Avalonia.Controls.Win32Properties; using static Avalonia.Controls.Win32Properties;
@ -21,5 +16,11 @@ namespace Avalonia.Controls.Platform
/// Gets or sets a custom callback for the window's WndProc /// Gets or sets a custom callback for the window's WndProc
/// </summary> /// </summary>
public CustomWndProcHookCallback? WndProcHookCallback { get; set; } public CustomWndProcHookCallback? WndProcHookCallback { get; set; }
/// <summary>
/// Sets a window corner preference for the window.
/// </summary>
/// <param name="preference">The value to set.</param>
public void SetWindowCornerPreference(WindowCornerPreference preference);
} }
} }

95
src/Avalonia.Controls/Platform/Win32Properties.cs

@ -17,9 +17,38 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public static class Win32Properties public static class Win32Properties
{ {
/// <summary>
/// Defines the <c>NonClientHitTestResult</c> attached property.
/// </summary>
public static readonly AttachedProperty<Win32HitTestValue> NonClientHitTestResultProperty =
AvaloniaProperty.RegisterAttached<Visual, Win32HitTestValue>(
"NonClientHitTestResult",
typeof(Win32Properties),
inherits: true,
defaultValue: Win32HitTestValue.Client);
/// <summary>
/// Defines the <c>WindowCornerPreferenceProperty</c> attached property.
/// </summary>
public static readonly AttachedProperty<WindowCornerPreference> WindowCornerPreferenceProperty =
AvaloniaProperty.RegisterAttached<Window, WindowCornerPreference>(
"WindowCornerPreference",
typeof(Win32Properties),
defaultValue: WindowCornerPreference.Default,
validate: ValidateWindowCornerPreference);
public delegate (uint style, uint exStyle) CustomWindowStylesCallback(uint style, uint exStyle); 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); public delegate IntPtr CustomWndProcHookCallback(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled);
static Win32Properties()
{
WindowCornerPreferenceProperty.Changed.AddClassHandler<Window>((window, e) =>
{
if (window.PlatformImpl is IWin32OptionsTopLevelImpl toplevelImpl)
toplevelImpl.SetWindowCornerPreference(e.GetNewValue<WindowCornerPreference>());
});
}
/// <summary> /// <summary>
/// Adds a callback to set the window's style. /// Adds a callback to set the window's style.
/// </summary> /// </summary>
@ -72,16 +101,48 @@ namespace Avalonia.Controls
} }
} }
public static readonly AttachedProperty<Win32HitTestValue> NonClientHitTestResultProperty = /// <summary>
AvaloniaProperty.RegisterAttached<Visual, Win32HitTestValue>( /// Sets the value of the <c>NonClientHitTestResult</c> attached property on the specified visual.
"NonClientHitTestResult", /// </summary>
typeof(Win32Properties), /// <param name="obj">The visual.</param>
inherits: true, /// <param name="value">The value to set.</param>
defaultValue: Win32HitTestValue.Client); public static void SetNonClientHitTestResult(Visual obj, Win32HitTestValue value)
=> obj.SetValue(NonClientHitTestResultProperty, value);
/// <summary>
/// Gets the value of the <c>NonClientHitTestResult</c> attached property on the specified visual.
/// </summary>
/// <param name="obj">The visual.</param>
/// <returns>The hit test value for <paramref name="obj"/>.</returns>
public static Win32HitTestValue GetNonClientHitTestResult(Visual obj)
=> obj.GetValue(NonClientHitTestResultProperty);
/// <summary>
/// Sets the value of the <c>WindowCornerPreference</c> attached property on the specified window.
/// </summary>
/// <param name="window">The window.</param>
/// <param name="value">The value to set.</param>
/// <remarks>
/// This value is supported starting with Windows 11 Build 22000.
/// It is ignored on earlier Windows versions.
/// </remarks>
public static void SetWindowCornerPreference(Window window, WindowCornerPreference value)
=> window.SetValue(WindowCornerPreferenceProperty, value);
/// <summary>
/// Gets the value of the <c>WindowCornerPreference</c> attached property on the specified window.
/// </summary>
/// <param name="window">The window.</param>
/// <returns>The window corner preference for <paramref name="window"/>.</returns>
public static WindowCornerPreference GetWindowCornerPreference(Window window)
=> window.GetValue(WindowCornerPreferenceProperty);
public static void SetNonClientHitTestResult(Visual obj, Win32HitTestValue value) => obj.SetValue(NonClientHitTestResultProperty, value); private static bool ValidateWindowCornerPreference(WindowCornerPreference preference)
public static Win32HitTestValue GetNonClientHitTestResult(Visual obj) => obj.GetValue(NonClientHitTestResultProperty); => preference is >= WindowCornerPreference.Default and <= WindowCornerPreference.RoundSmall;
/// <summary>
/// Represents a hit test value for a visual.
/// </summary>
public enum Win32HitTestValue public enum Win32HitTestValue
{ {
Nowhere = 0, Nowhere = 0,
@ -99,5 +160,23 @@ namespace Avalonia.Controls
BottomRight = 17, BottomRight = 17,
Close = 20, Close = 20,
} }
/// <summary>
/// Represents the rounded corner preference for a window.
/// </summary>
public enum WindowCornerPreference
{
/// <summary>Let the system decide when to round window corners.</summary>
Default = 0,
/// <summary>Never round window corners.</summary>
DoNotRound = 1,
/// <summary>Round the corners, if appropriate.</summary>
Round = 2,
/// <summary>Round the corners if appropriate, with a small radius.</summary>
RoundSmall = 3
}
} }
} }

35
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -108,6 +108,7 @@ namespace Avalonia.Win32
internal bool _ignoreWmChar; internal bool _ignoreWmChar;
private WindowTransparencyLevel _transparencyLevel; private WindowTransparencyLevel _transparencyLevel;
private readonly WindowTransparencyLevel _defaultTransparencyLevel; private readonly WindowTransparencyLevel _defaultTransparencyLevel;
private WindowCornerPreference _cornerPreference;
private const int MaxPointerHistorySize = 512; private const int MaxPointerHistorySize = 512;
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(); private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new();
@ -1197,6 +1198,18 @@ namespace Avalonia.Win32
return margins; 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() private void ExtendClientArea()
{ {
if (!_shown) 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. // Make sure that Windows still paints the non-client area so we get native borders and shadows.
SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_ENABLED); SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_ENABLED);
// On Windows 11 21H2 and later, corners are configurable.
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
SetWindowCornerPreference(DwmWindowCornerPreference.DWMWCP_ROUND);
} }
else else
{ {
@ -1231,16 +1240,7 @@ namespace Avalonia.Win32
_extendedMargins = new Thickness(); _extendedMargins = new Thickness();
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000))
SetWindowCornerPreference(DwmWindowCornerPreference.DWMWCP_DEFAULT);
SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_USEWINDOWSTYLE); SetNCRenderingPolicy(DwmNCRenderingPolicy.DWMNCRP_USEWINDOWSTYLE);
unsafe
{
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int));
}
} }
if (!_isClientAreaExtended) if (!_isClientAreaExtended)
@ -1262,9 +1262,6 @@ namespace Avalonia.Win32
ExtendClientAreaToDecorationsChanged?.Invoke(_isClientAreaExtended); 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) private unsafe void SetNCRenderingPolicy(DwmNCRenderingPolicy value)
=> DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_NCRENDERING_POLICY, &value, sizeof(int)); => DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_NCRENDERING_POLICY, &value, sizeof(int));
@ -1615,6 +1612,12 @@ namespace Avalonia.Win32
ExtendClientArea(); ExtendClientArea();
} }
public void SetWindowCornerPreference(WindowCornerPreference preference)
{
_cornerPreference = preference;
UpdateWindowCornerPreference();
}
/// <inheritdoc/> /// <inheritdoc/>
public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended; public bool IsClientAreaExtendedToDecorations => _isClientAreaExtended;

Loading…
Cancel
Save