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中
    }
}

运行效果

image

 

posted on 2026-03-23 10:55  dalgleish  阅读(3)  评论(0)    收藏  举报