ModernWindowChrome控件cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using System;
namespace Shares.Avalonia.CustomControls
{
[TemplatePart("PART_TitleBar", typeof(Control))]
[TemplatePart("PART_CloseButton", typeof(Button))]
[TemplatePart("PART_GripBottomRight", typeof(Control))]
[TemplatePart("PART_EdgeLeft", typeof(Control))]
[TemplatePart("PART_EdgeRight", typeof(Control))]
[TemplatePart("PART_EdgeTop", typeof(Control))]
[TemplatePart("PART_EdgeBottom", typeof(Control))]
[TemplatePart("PART_CornerTopLeft", typeof(Control))]
[TemplatePart("PART_CornerTopRight", typeof(Control))]
[TemplatePart("PART_CornerBottomLeft", typeof(Control))]
[TemplatePart("PART_CornerBottomRight", typeof(Control))]
public class ModernWindowChrome : TemplatedControl
{
public static readonly AttachedProperty<object?> FooterProperty =
AvaloniaProperty.RegisterAttached<ModernWindowChrome, Window, object?>("Footer");
public static void SetFooter(Window element, object? value) => element.SetValue(FooterProperty, value);
public static object? GetFooter(Window element) => element.GetValue(FooterProperty);
[Flags]
private enum ResizeEdges
{
None = 0,
Left = 1,
Top = 2,
Right = 4,
Bottom = 8
}
private bool isResizing;
private ResizeEdges edges;
private PixelPoint startWindowPos;
private Size startWindowSize;
private PixelPoint startPointerScreenPx;
private double startRenderScaling;
private Control? titleBar;
private Button? closeButton;
private Control?[]? resizeParts;
private IInputElement? captured;
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
Unhook();
titleBar = e.NameScope.Find<Control>("PART_TitleBar");
closeButton = e.NameScope.Find<Button>("PART_CloseButton");
resizeParts = new[]
{
e.NameScope.Find<Control>("PART_GripBottomRight"),
e.NameScope.Find<Control>("PART_EdgeLeft"),
e.NameScope.Find<Control>("PART_EdgeRight"),
e.NameScope.Find<Control>("PART_EdgeTop"),
e.NameScope.Find<Control>("PART_EdgeBottom"),
e.NameScope.Find<Control>("PART_CornerTopLeft"),
e.NameScope.Find<Control>("PART_CornerTopRight"),
e.NameScope.Find<Control>("PART_CornerBottomLeft"),
e.NameScope.Find<Control>("PART_CornerBottomRight"),
};
if (titleBar is not null)
titleBar.PointerPressed += TitleBarPointerPressed;
if (closeButton is not null)
closeButton.Click += CloseButtonClick;
foreach (var part in resizeParts)
{
if (part is null)
continue;
part.PointerPressed += ResizePointerPressed;
part.PointerMoved += ResizePointerMoved;
part.PointerReleased += ResizePointerReleased;
}
}
private void TitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
{
var window = TopLevel.GetTopLevel(this) as Window;
if (window is null)
return;
if (sender is not Control c)
return;
if (e.GetCurrentPoint(c).Properties.IsLeftButtonPressed)
window.BeginMoveDrag(e);
}
private void CloseButtonClick(object? sender, RoutedEventArgs e)
{
var window = TopLevel.GetTopLevel(this) as Window;
window?.Close();
}
private void ResizePointerPressed(object? sender, PointerPressedEventArgs e)
{
var window = TopLevel.GetTopLevel(this) as Window;
if (window is null)
return;
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
if (sender is not Control c)
return;
edges = ParseEdges(c.Tag as string);
if (edges == ResizeEdges.None)
return;
var topLevel = TopLevel.GetTopLevel(window);
if (topLevel is null)
return;
isResizing = true;
startWindowPos = window.Position;
startWindowSize = new Size(window.Bounds.Width, window.Bounds.Height);
startRenderScaling = window.RenderScaling;
startPointerScreenPx = GetPointerScreenPx(e, topLevel);
captured = c;
e.Pointer.Capture(c);
e.Handled = true;
}
private void ResizePointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!isResizing)
return;
isResizing = false;
edges = ResizeEdges.None;
if (e.Pointer?.Captured == captured)
e.Pointer?.Capture(null);
captured = null;
e.Handled = true;
}
private void ResizePointerMoved(object? sender, PointerEventArgs e)
{
if (!isResizing || edges == ResizeEdges.None)
return;
var window = TopLevel.GetTopLevel(this) as Window;
if (window is null)
return;
var topLevel = TopLevel.GetTopLevel(window);
if (topLevel is null)
return;
var pointerScreenPx = GetPointerScreenPx(e, topLevel);
var dxPx = pointerScreenPx.X - startPointerScreenPx.X;
var dyPx = pointerScreenPx.Y - startPointerScreenPx.Y;
var dxDip = dxPx / startRenderScaling;
var dyDip = dyPx / startRenderScaling;
var newX = startWindowPos.X;
var newY = startWindowPos.Y;
var newW = startWindowSize.Width;
var newH = startWindowSize.Height;
var minWidth = window.MinWidth;
var minHeight = window.MinHeight;
var hasLeft = (edges & ResizeEdges.Left) != 0;
var hasRight = (edges & ResizeEdges.Right) != 0;
var hasTop = (edges & ResizeEdges.Top) != 0;
var hasBottom = (edges & ResizeEdges.Bottom) != 0;
if (hasRight)
newW = Math.Max(minWidth, startWindowSize.Width + dxDip);
if (hasBottom)
newH = Math.Max(minHeight, startWindowSize.Height + dyDip);
if (hasLeft)
{
newW = Math.Max(minWidth, startWindowSize.Width - dxDip);
newX = startWindowPos.X + dxPx;
if (newW <= minWidth)
newX = startWindowPos.X + ClampDeltaPxForMin(startWindowSize.Width, minWidth);
}
if (hasTop)
{
newH = Math.Max(minHeight, startWindowSize.Height - dyDip);
newY = startWindowPos.Y + dyPx;
if (newH <= minHeight)
newY = startWindowPos.Y + ClampDeltaPxForMin(startWindowSize.Height, minHeight);
}
window.Position = new PixelPoint(newX, newY);
window.Width = newW;
window.Height = newH;
e.Handled = true;
}
private void Unhook()
{
if (titleBar is not null)
titleBar.PointerPressed -= TitleBarPointerPressed;
if (closeButton is not null)
closeButton.Click -= CloseButtonClick;
if (resizeParts is null)
return;
foreach (var part in resizeParts)
{
if (part is null)
continue;
part.PointerPressed -= ResizePointerPressed;
part.PointerMoved -= ResizePointerMoved;
part.PointerReleased -= ResizePointerReleased;
}
}
private static ResizeEdges ParseEdges(string? tag)
{
return tag switch
{
"Left" => ResizeEdges.Left,
"Right" => ResizeEdges.Right,
"Top" => ResizeEdges.Top,
"Bottom" => ResizeEdges.Bottom,
"TopLeft" => ResizeEdges.Top | ResizeEdges.Left,
"TopRight" => ResizeEdges.Top | ResizeEdges.Right,
"BottomLeft" => ResizeEdges.Bottom | ResizeEdges.Left,
"BottomRight" => ResizeEdges.Bottom | ResizeEdges.Right,
_ => ResizeEdges.None
};
}
private static PixelPoint GetPointerScreenPx(PointerEventArgs e, TopLevel topLevel)
{
var pInTopLevel = e.GetPosition(topLevel);
return topLevel.PointToScreen(pInTopLevel);
}
private int ClampDeltaPxForMin(double startSizeDip, double minSizeDip)
{
var clampPx = (startSizeDip - minSizeDip) * startRenderScaling;
return (int)Math.Round(clampPx);
}
}
}
ModernWindowChrome控件style代码
xmlns:local="using:Shares.Avalonia.CustomControls"
<!--Window样式--> <Style Selector="Window.ModernWindow"> <Setter Property="SystemDecorations" Value="None"/> <Setter Property="TransparencyLevelHint" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/> <Setter Property="MinWidth" Value="150"/> <Setter Property="MinHeight" Value="120"/> <!-- Window 模板 --> <Setter Property="Template"> <ControlTemplate> <!-- Chrome 作为根 --> <local:ModernWindowChrome> <!-- 给 Chrome 内联一个 ControlTheme --> <local:ModernWindowChrome.Theme> <ControlTheme TargetType="local:ModernWindowChrome"> <Setter Property="Template"> <ControlTemplate> <Border x:Name="windowFrame" BorderBrush="#395984" BorderThickness="1" CornerRadius="0,20,30,40"> <Border.Background> <LinearGradientBrush> <GradientStop Color="#E7EBF7" Offset="0"/> <GradientStop Color="#CEE3FF" Offset="0.5"/> </LinearGradientBrush> </Border.Background> <Grid RowDefinitions="auto,*,auto"> <!-- 标题栏 --> <Grid Grid.Row="0" x:Name="PART_TitleBar" Margin="1" Background="Transparent" ColumnDefinitions="*,auto"> <TextBlock Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" Padding="5" Text="{Binding $parent[Window].Title}"/> <Button Grid.Column="1" x:Name="PART_CloseButton" Margin="4" Padding="6,2" VerticalAlignment="Center" Content="x"/> </Grid> <!-- 内容区 --> <Grid Grid.Row="1" Background="#B5CBEF"> <ContentPresenter Content="{Binding $parent[Window].Content}"/> </Grid> <!-- Footer + 右下角 Grip --> <Grid Grid.Row="2" Margin="2" ColumnDefinitions="*,auto" Background="Transparent"> <ContentPresenter Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" Content="{Binding $parent[Window].(local:ModernWindowChrome.Footer)}"/> <Border Grid.Column="1" x:Name="PART_GripBottomRight" Width="16" Height="16" Margin="5" Background="Transparent" HorizontalAlignment="Right" VerticalAlignment="Bottom" Tag="BottomRight"> <Rectangle Margin="1"> <Rectangle.Fill> <VisualBrush TileMode="Tile" DestinationRect="0,0,4,4" SourceRect="0,0,8,8"> <VisualBrush.Visual> <Canvas Width="8" Height="8"> <Rectangle Width="4" Height="4" Canvas.Left="4" Canvas.Top="4" Fill="#AAA"/> </Canvas> </VisualBrush.Visual> </VisualBrush> </Rectangle.Fill> </Rectangle> </Border> </Grid> <!-- 四边 --> <Rectangle x:Name="PART_EdgeLeft" Width="5" Fill="Transparent" HorizontalAlignment="Left" VerticalAlignment="Stretch" Cursor="SizeWestEast" Tag="Left" Grid.RowSpan="3"/> <Rectangle x:Name="PART_EdgeRight" Width="5" Fill="Transparent" HorizontalAlignment="Right" VerticalAlignment="Stretch" Cursor="SizeWestEast" Tag="Right" Grid.RowSpan="3"/> <Rectangle x:Name="PART_EdgeTop" Height="5" Fill="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Top" Cursor="SizeNorthSouth" Tag="Top" Grid.RowSpan="3"/> <Rectangle x:Name="PART_EdgeBottom" Height="5" Fill="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Cursor="SizeNorthSouth" Tag="Bottom" Grid.RowSpan="3"/> <!-- 四角 --> <Rectangle x:Name="PART_CornerTopLeft" Width="10" Height="10" Fill="Transparent" HorizontalAlignment="Left" VerticalAlignment="Top" Cursor="SizeAll" Tag="TopLeft" Grid.RowSpan="3"/> <Rectangle x:Name="PART_CornerTopRight" Width="10" Height="10" Fill="Transparent" HorizontalAlignment="Right" VerticalAlignment="Top" Cursor="SizeAll" Tag="TopRight" Grid.RowSpan="3"/> <Rectangle x:Name="PART_CornerBottomLeft" Width="10" Height="10" Fill="Transparent" HorizontalAlignment="Left" VerticalAlignment="Bottom" Cursor="SizeAll" Tag="BottomLeft" Grid.RowSpan="3"/> <Rectangle x:Name="PART_CornerBottomRight" Width="10" Height="10" Fill="Transparent" HorizontalAlignment="Right" VerticalAlignment="Bottom" Cursor="SizeAll" Tag="BottomRight" Grid.RowSpan="3"/> </Grid> </Border> </ControlTemplate> </Setter> </ControlTheme> </local:ModernWindowChrome.Theme> </local:ModernWindowChrome> </ControlTemplate> </Setter> </Style>
ModernWindowTest.axaml代码
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="300" Width="300" Classes="ModernWindow" x:Class="AvaloniaUI.ModernWindowTest" Title="ModernWindowTest"> <ModernWindowChrome.Footer> <StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Center"> <Button Content="OK"/> <Button Content="Cancel"/> </StackPanel> </ModernWindowChrome.Footer> Welcome to Avalonia! </Window>
ModernWindowTest.axaml.cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Shares.Avalonia;
namespace AvaloniaUI;
public partial class ModernWindowTest : Window
{
public ModernWindowTest()
{
InitializeComponent();
this.Load("avares://Shares/Avalonia/Styles/Styles.axaml");//这里是置顶里写的扩展,如果不用,可以自行加到App.axaml中
}
}
运行效果

浙公网安备 33010602011771号