UniformSpacingPanel Avalonia版本


UniformSpacingPanel是HandyControl非常好用的一个容器,具备自动换行、等间距等特性,我一直用它来作为控制栏、属性编辑窗口的容器。

刚看了下HandyControl官网甚至没有文档…UniformSpacingPanel真的是一个非常好用的容器,可以像StackPanel一样设置元素向某一个方向排列,并且通过设置Spacing 设置元素间的间距,同时还支持自动换行,当元素的时候可以通过这个控件实现容器自适应宽高变化。
但是HandyControl中并不支持Avalonia,所以尝试按照其源码改写了一个简单的Avalonia版本,暂时没有添加ItemWidth 、ItemHeight等属性 基本满足个人的使用需求,供大家参考。
在这里插入图片描述

下面是代码片段,需要注意的是在WPF中是通过InternalChildren获得UIElement类型的子对象。Avalonia中我采用Control代替,不确定是否合适,目前没有问题,可以根据实际情况自行修改。

using Avalonia.Layout;

namespace Infrastructure.Avalonia;

public class UniformSpacingPanel : Panel
{
    public enum Wrapping
    {
        None,
        Wrap
    }

    public static readonly StyledProperty<Orientation> OrientationProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, Orientation>(nameof(Orientation), Orientation.Horizontal);

    public static readonly StyledProperty<Wrapping> ChildWrappingProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, Wrapping>(nameof(ChildWrapping), Wrapping.Wrap);

    public static readonly StyledProperty<double> SpacingProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, double>(nameof(Spacing), 0);

    public static readonly StyledProperty<double> HorizontalSpacingProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, double>(nameof(HorizontalSpacing), 0);

    public static readonly StyledProperty<double> VerticalSpacingProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, double>(nameof(VerticalSpacing), 0);

    public static readonly StyledProperty<double> ItemWidthProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, double>(nameof(ItemWidth), double.NaN);

    public static readonly StyledProperty<double> ItemHeightProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, double>(nameof(ItemHeight), double.NaN);

    public static readonly StyledProperty<HorizontalAlignment> ItemHorizontalAlignmentProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, HorizontalAlignment>(nameof(ItemHorizontalAlignment),
            HorizontalAlignment.Stretch);

    public static readonly StyledProperty<VerticalAlignment> ItemVerticalAlignmentProperty =
        AvaloniaProperty.Register<UniformSpacingPanel, VerticalAlignment>(nameof(ItemVerticalAlignment),
            VerticalAlignment.Stretch);

    public Orientation Orientation
    {
        get => GetValue(OrientationProperty);
        set => SetValue(OrientationProperty, value);
    }

    public Wrapping ChildWrapping
    {
        get => GetValue(ChildWrappingProperty);
        set => SetValue(ChildWrappingProperty, value);
    }

    public double Spacing
    {
        get => GetValue(SpacingProperty);
        set => SetValue(SpacingProperty, value);
    }

    public double HorizontalSpacing
    {
        get => GetValue(HorizontalSpacingProperty);
        set => SetValue(HorizontalSpacingProperty, value);
    }

    public double VerticalSpacing
    {
        get => GetValue(VerticalSpacingProperty);
        set => SetValue(VerticalSpacingProperty, value);
    }

    public double ItemWidth
    {
        get => GetValue(ItemWidthProperty);
        set => SetValue(ItemWidthProperty, value);
    }

    public double ItemHeight
    {
        get => GetValue(ItemHeightProperty);
        set => SetValue(ItemHeightProperty, value);
    }

    public HorizontalAlignment ItemHorizontalAlignment
    {
        get => GetValue(ItemHorizontalAlignmentProperty);
        set => SetValue(ItemHorizontalAlignmentProperty, value);
    }

    public VerticalAlignment ItemVerticalAlignment
    {
        get => GetValue(ItemVerticalAlignmentProperty);
        set => SetValue(ItemVerticalAlignmentProperty, value);
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        var uvConstraint = new PanelUvSize(Orientation, availableSize);
        var panelSize = new PanelUvSize(Orientation);
        double spacing = Spacing;
        bool firstChild = true;

        foreach (Control child in Children)
        {
            child.Measure(availableSize);
            var sz = new PanelUvSize(Orientation, child.DesiredSize);

            if (Orientation == Orientation.Horizontal)
            {
                double nextU = firstChild ? sz.U : sz.U + spacing; // 考虑Spacing
                if (panelSize.U + nextU > uvConstraint.U) // 检查是否有足够空间放下当前子元素
                {
                    panelSize.V += panelSize.U > 0 ? sz.V : 0; // 开始新行
                    panelSize.U = sz.U;
                    firstChild = true; // 重置首元素标志
                }
                else
                {
                    panelSize.U += nextU;
                    panelSize.V = Math.Max(panelSize.V, sz.V);
                    firstChild = false;
                }
            }
            else
            {
                double nextV = firstChild ? sz.V : sz.V + spacing; // 考虑Spacing
                if (panelSize.V + nextV > uvConstraint.V) // 检查是否有足够空间放下当前子元素
                {
                    panelSize.U += panelSize.V > 0 ? sz.U : 0; // 开始新列
                    panelSize.V = sz.V;
                    firstChild = true; // 重置首元素标志
                }
                else
                {
                    panelSize.V += nextV;
                    panelSize.U = Math.Max(panelSize.U, sz.U);
                    firstChild = false;
                }
            }
        }

        return new Size(panelSize.Width, panelSize.Height);
    }

protected override Size ArrangeOverride(Size finalSize)
{
    var origin = new Point(0, 0);
    var lineStart = 0;
    var uvFinalSize = new PanelUvSize(Orientation, finalSize);
    var currentLine = new PanelUvSize(Orientation);
    double accumulatedSize = 0;  // 用于追踪当前行或列的累计尺寸,包括间隔

    for (int i = 0; i < Children.Count; i++)
    {
        var child = Children[i] as Control;
        var sz = new PanelUvSize(Orientation, child.DesiredSize);
        double nextSize = (Orientation == Orientation.Horizontal ? sz.U : sz.V);

        // 确定是否需要添加间隔
        if (i > lineStart) {
            nextSize += Spacing;  // 如果不是行或列的第一个元素,则添加间隔
        }

        // 检查是否需要换行或换列
        if ((Orientation == Orientation.Horizontal && accumulatedSize + nextSize > uvFinalSize.U) ||
            (Orientation == Orientation.Vertical && accumulatedSize + nextSize > uvFinalSize.V))
        {
            ArrangeLine(lineStart, i, origin.X, origin.Y, currentLine);
            origin = Orientation == Orientation.Horizontal ? new Point(0, origin.Y + currentLine.V) : new Point(origin.X + currentLine.U, 0);
            currentLine = new PanelUvSize(Orientation);  // 重置当前行或列尺寸
            lineStart = i;  // 更新行或列的开始索引
            accumulatedSize = 0;  // 重置累计尺寸
            i--;  // 重新考虑当前元素,因为它需要放到新的行或列
            continue;
        }

        // 更新当前行或列的尺寸
        accumulatedSize += nextSize;
        if (Orientation == Orientation.Horizontal) {
            currentLine.U = accumulatedSize;
            currentLine.V = Math.Max(currentLine.V, sz.V);
        } else {
            currentLine.V = accumulatedSize;
            currentLine.U = Math.Max(currentLine.U, sz.U);
        }
    }

    // 处理最后一行或列
    ArrangeLine(lineStart, Children.Count, origin.X, origin.Y, currentLine);

    return finalSize;
}

private void ArrangeLine(int start, int end, double originX, double originY, PanelUvSize currentLine)
{
    double pos = 0;
    for (int i = start; i < end; i++)
    {
        var child = Children[i] as Control;
        double childSize = Orientation == Orientation.Horizontal ? child.DesiredSize.Width : child.DesiredSize.Height;
        if (i != start) pos += Spacing;
        if (Orientation == Orientation.Horizontal) {
            child.Arrange(new Rect(originX + pos, originY, child.DesiredSize.Width, currentLine.V));
            pos += child.DesiredSize.Width;
        } else {
            child.Arrange(new Rect(originX, originY + pos, currentLine.U, child.DesiredSize.Height));
            pos += child.DesiredSize.Height;
        }
    }
}

}
using Avalonia.Layout;

namespace Infrastructure.Avalonia;


internal struct PanelUvSize
{
    private readonly Orientation _orientation;

    public Size ScreenSize => new(U, V);

    public double U { get; set; }

    public double V { get; set; }

    public double Width
    {
        get => _orientation == Orientation.Horizontal ? U : V;
        set
        {
            if (_orientation == Orientation.Horizontal)
            {
                U = value;
            }
            else
            {
                V = value;
            }
        }
    }

    public double Height
    {
        get => _orientation == Orientation.Horizontal ? V : U;
        set
        {
            if (_orientation == Orientation.Horizontal)
            {
                V = value;
            }
            else
            {
                U = value;
            }
        }
    }

    public PanelUvSize(Orientation orientation, double width, double height)
    {
        U = V = 0d;
        _orientation = orientation;
        Width = width;
        Height = height;
    }

    public PanelUvSize(Orientation orientation, Size size)
    {
        U = V = 0d;
        _orientation = orientation;
        Width = size.Width;
        Height = size.Height;
    }

    public PanelUvSize(Orientation orientation)
    {
        U = V = 0d;
        _orientation = orientation;
    }
}
 <TabItem Header="UniformSpacingPanel">
            <TabItem.Styles>
                <Style Selector="Button">
                    <Setter Property="Width" Value="100"></Setter>
                </Style>
                <Style Selector="Border">
                    <Setter Property="BorderBrush" Value="Gray"></Setter>
                    <Setter Property="BorderThickness" Value="2"></Setter>
                </Style>
            </TabItem.Styles>
        <StackPanel Orientation="Horizontal">
            <Border Width="500">
                <avalonia:UniformSpacingPanel Spacing="10" ChildWrapping="Wrap"  Orientation="Horizontal">
                    <Button>1</Button>
                    <Button>2</Button>
                    <Button>3</Button>
                    <Button>4</Button>
                    <Button>5</Button>
                    <RadioButton>6</RadioButton>
                    <CheckBox>7</CheckBox>
                </avalonia:UniformSpacingPanel>
            </Border>
            
            <Border Height="500" Width="300">
                <avalonia:UniformSpacingPanel Spacing="10" ChildWrapping="Wrap"  Orientation="Horizontal">
                    <Button>1</Button>
                    <Button>2</Button>
                    <Button>3</Button>
                    <Button>4</Button>
                    <Button>5</Button>
                    <RadioButton>6</RadioButton>
                    <CheckBox>7</CheckBox>
                </avalonia:UniformSpacingPanel>
            </Border>
            
            <Border Height="150">
                <avalonia:UniformSpacingPanel Spacing="10" ChildWrapping="Wrap"  Orientation="Vertical">
                    <Button>1</Button>
                    <Button>2</Button>
                    <Button>3</Button>
                    <Button>4</Button>
                    <Button>5</Button>
                    <RadioButton>6</RadioButton>
                    <CheckBox>7</CheckBox>
                </avalonia:UniformSpacingPanel>
            </Border>
        </StackPanel>
        </TabItem>
  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值