Files
Shrlalgo.RvKits/Melskin/Layout/WrapPanel.cs

405 lines
16 KiB
C#
Raw Normal View History

2025-07-31 20:12:24 +08:00
using System.Windows.Markup;
2026-01-02 17:30:41 +08:00
namespace Melskin.Layout;
2025-07-31 20:12:24 +08:00
/// <summary>
/// WrapPanel 是一种布局面板,它允许子元素根据指定的方向(水平或垂直)自动换行排列。
/// 该面板提供了一种灵活的方式来控制子元素的宽度、高度以及它们之间的间距。
/// </summary>
/// <remarks>
/// 通过设置 Orientation 属性,可以指定子元素的排列方向是水平还是垂直。
/// ItemWidth 和 ItemHeight 属性用于统一设置所有子元素的宽度和高度。
/// 如果不需要统一设置,则可以忽略这两个属性,每个子元素将根据其自身的内容大小进行布局。
/// VerticalSpacing 和 HorizontalSpacing 属性分别定义了垂直和水平方向上相邻子元素间的间距。
/// </remarks>
[ContentProperty(nameof(Children))]
public class WrapPanel : Panel
{
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中子元素的排列方向属性。通过设置此属性可以控制子元素是水平排列还是垂直排列。
/// </summary>
2025-07-31 20:12:24 +08:00
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(WrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中子元素的排列方向属性。通过设置此属性可以控制子元素是水平排列还是垂直排列。
/// </summary>
2025-07-31 20:12:24 +08:00
public Orientation Orientation
{
get => (Orientation)GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中所有子元素的统一宽度属性。通过设置此属性可以为面板中的所有子元素指定相同的宽度。
/// </summary>
2025-07-31 20:12:24 +08:00
public static readonly DependencyProperty ItemWidthProperty =
DependencyProperty.Register(nameof(ItemWidth), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中每个子元素的宽度。通过设置此属性可以统一控制面板内所有子元素的宽度。如果设置了该值则所有子元素将具有相同的宽度若未设置即保持默认值NaN则每个子元素将根据其自身内容确定宽度。
/// </summary>
2025-07-31 20:12:24 +08:00
public double ItemWidth
{
get => (double)GetValue(ItemWidthProperty);
set => SetValue(ItemWidthProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中所有子元素的统一高度属性。通过设置此属性可以控制面板内所有子元素的高度一致。
/// 如果不需要统一设置,则可以忽略此属性,每个子元素将根据其自身的内容大小进行布局。
/// </summary>
2025-07-31 20:12:24 +08:00
public static readonly DependencyProperty ItemHeightProperty =
DependencyProperty.Register(nameof(ItemHeight), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(double.NaN, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中每个子元素的高度属性。通过设置此属性可以统一控制所有子元素的高度。
/// 如果未设置或设置为NaN则子元素将使用其自身的高度。
/// </summary>
2025-07-31 20:12:24 +08:00
public double ItemHeight
{
get => (double)GetValue(ItemHeightProperty);
set => SetValue(ItemHeightProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示垂直方向上相邻子元素之间的间距属性。通过设置此属性可以控制WrapPanel中垂直排列的子元素间的距离。
/// </summary>
2025-07-31 20:12:24 +08:00
public static readonly DependencyProperty VerticalSpacingProperty =
DependencyProperty.Register(nameof(VerticalSpacing), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中子元素之间的垂直间距属性。通过设置此属性可以控制子元素在垂直方向上的间距大小。
/// </summary>
2025-07-31 20:12:24 +08:00
public double VerticalSpacing
{
get => (double)GetValue(VerticalSpacingProperty);
set => SetValue(VerticalSpacingProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中子元素在水平方向上的间距属性。通过设置此属性可以控制相邻子元素之间的水平间距。
/// </summary>
2025-07-31 20:12:24 +08:00
public static readonly DependencyProperty HorizontalSpacingProperty =
DependencyProperty.Register(nameof(HorizontalSpacing), typeof(double), typeof(WrapPanel), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示WrapPanel中子元素在水平方向上的间距属性。通过设置此属性可以控制子元素之间的水平距离。
/// </summary>
2025-07-31 20:12:24 +08:00
public double HorizontalSpacing
{
get => (double)GetValue(HorizontalSpacingProperty);
set => SetValue(HorizontalSpacingProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <inheritdoc />
2025-07-31 20:12:24 +08:00
protected override Size MeasureOverride(Size availableSize)
{
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var verticalSpacing = VerticalSpacing;
var horizontalSpacing = HorizontalSpacing;
var offsetX = 0d;
var offsetY = 0d;
var maxLineLength = 0d;
var currentLineSize = 0d;
double currentLineLength;
Func<Size, double> childWidthGetter = double.IsNaN(itemWidth) ? size => size.Width : _ => itemWidth;
Func<Size, double> childHeightGetter = double.IsNaN(itemHeight) ? size => size.Height : _ => itemHeight;
var internalChildren = InternalChildren;
if (Orientation == Orientation.Horizontal)
{
availableSize.Height = double.PositiveInfinity;
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
child.Measure(availableSize);
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
offsetX += childWidth;
if (child.Visibility != Visibility.Collapsed)
{
offsetX += horizontalSpacing;
}
if (offsetX - horizontalSpacing > availableSize.Width)
{
currentLineLength = offsetX - horizontalSpacing - childWidth - horizontalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetX = childWidth + horizontalSpacing;
offsetY += currentLineSize;
offsetY += verticalSpacing;
currentLineSize = 0;
}
if (childHeight > currentLineSize)
{
currentLineSize = childHeight;
}
}
currentLineLength = offsetX - horizontalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetY += currentLineSize;
offsetY += verticalSpacing;
return new Size(maxLineLength, offsetY - verticalSpacing);
}
else
{
availableSize.Width = double.PositiveInfinity;
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
child.Measure(availableSize);
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
offsetY += childHeight;
if (child.Visibility != Visibility.Collapsed)
{
offsetY += verticalSpacing;
}
if (offsetY - verticalSpacing > availableSize.Height)
{
currentLineLength = offsetY - horizontalSpacing - childHeight - verticalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetY = childHeight + verticalSpacing;
offsetX += currentLineSize;
offsetX += horizontalSpacing;
currentLineSize = 0;
}
if (childWidth > currentLineSize)
currentLineSize = childWidth;
}
currentLineLength = offsetY - verticalSpacing;
if (currentLineLength > maxLineLength)
{
maxLineLength = currentLineLength;
}
offsetX += currentLineSize;
offsetX += horizontalSpacing;
return new Size(offsetX - horizontalSpacing, maxLineLength);
}
}
2025-08-20 12:10:13 +08:00
/// <inheritdoc />
2025-07-31 20:12:24 +08:00
protected override Size ArrangeOverride(Size finalSize)
{
var itemWidth = ItemWidth;
var itemHeight = ItemHeight;
var verticalSpacing = VerticalSpacing;
var horizontalSpacing = HorizontalSpacing;
var tempOffset = 0d;
2025-08-20 12:10:13 +08:00
Func<Size, double> childWidthGetter = double.IsNaN(itemWidth) ? size => size.Width : _ => itemWidth;
Func<Size, double> childHeightGetter = double.IsNaN(itemHeight) ? size => size.Height : _ => itemHeight;
2025-07-31 20:12:24 +08:00
var internalChildren = InternalChildren;
var currentLineSize = 0d;
var currentLineOffsetX = 0d;
var currentLineOffsetY = 0d;
var currentLineIndexStart = 0;
if (Orientation == Orientation.Horizontal)
{
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
tempOffset += childWidth;
if (child.Visibility != Visibility.Collapsed)
{
tempOffset += horizontalSpacing;
}
if (tempOffset - horizontalSpacing > finalSize.Width)
{
ArrangeLineHorizontal(
internalChildren,
currentLineIndexStart,
i,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
horizontalSpacing,
childWidthGetter,
childHeightGetter);
currentLineOffsetX = 0;
currentLineOffsetY += currentLineSize;
currentLineOffsetY += verticalSpacing;
currentLineIndexStart = i;
currentLineSize = 0;
tempOffset = childWidth + horizontalSpacing;
}
if (childHeight > currentLineSize)
{
currentLineSize = childHeight;
}
}
ArrangeLineHorizontal(
internalChildren,
currentLineIndexStart,
internalChildren.Count,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
horizontalSpacing,
childWidthGetter,
childHeightGetter);
}
else
{
for (var i = 0; i < internalChildren.Count; i++)
{
var child = internalChildren[i];
var childDesiredSize = child.DesiredSize;
var childWidth = childWidthGetter.Invoke(childDesiredSize);
var childHeight = childHeightGetter.Invoke(childDesiredSize);
tempOffset += childHeight;
if (child.Visibility != Visibility.Collapsed)
{
tempOffset += verticalSpacing;
}
if (tempOffset - verticalSpacing > finalSize.Height)
{
ArrangeLineVertical(
internalChildren,
currentLineIndexStart,
i,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
verticalSpacing,
childWidthGetter,
childHeightGetter);
currentLineOffsetY = 0;
currentLineOffsetX += currentLineSize;
currentLineOffsetX += horizontalSpacing;
currentLineIndexStart = i;
currentLineSize = 0;
tempOffset = childHeight + verticalSpacing;
}
if (childWidth > currentLineSize)
{
currentLineSize = childWidth;
}
}
ArrangeLineVertical(
internalChildren,
currentLineIndexStart,
internalChildren.Count,
currentLineOffsetX,
currentLineOffsetY,
currentLineSize,
verticalSpacing,
childWidthGetter,
childHeightGetter);
}
return finalSize;
static void ArrangeLineHorizontal(
UIElementCollection children,
int childIndexStart,
int childIndexEnd,
double currentLineOffsetX,
double currentLineOffsetY,
double currentLineSize,
double spacing,
Func<Size, double> widthGetter,
Func<Size, double> heightGetter)
{
var lineChildOffset = 0d;
for (var j = childIndexStart; j < childIndexEnd; j++)
{
var lineChild = children[j];
var lineChildDesiredSize = lineChild.DesiredSize;
var lineChildWidth = widthGetter.Invoke(lineChildDesiredSize);
2025-08-20 12:10:13 +08:00
heightGetter.Invoke(lineChildDesiredSize);
2025-07-31 20:12:24 +08:00
lineChild.Arrange(new Rect(currentLineOffsetX + lineChildOffset, currentLineOffsetY, lineChildWidth, currentLineSize));
lineChildOffset += lineChildWidth;
if (lineChild.Visibility != Visibility.Collapsed)
{
lineChildOffset += spacing;
}
}
}
static void ArrangeLineVertical(
UIElementCollection children,
int childIndexStart,
int childIndexEnd,
double currentLineOffsetX,
double currentLineOffsetY,
double currentLineSize,
double spacing,
Func<Size, double> widthGetter,
Func<Size, double> heightGetter)
{
var lineChildOffset = 0d;
for (var j = childIndexStart; j < childIndexEnd; j++)
{
var lineChild = children[j];
var lineChildDesiredSize = lineChild.DesiredSize;
2025-08-20 12:10:13 +08:00
widthGetter.Invoke(lineChildDesiredSize);
2025-07-31 20:12:24 +08:00
var lineChildHeight = heightGetter.Invoke(lineChildDesiredSize);
lineChild.Arrange(new Rect(currentLineOffsetX, currentLineOffsetY + lineChildOffset, currentLineSize, lineChildHeight));
lineChildOffset += lineChildHeight;
if (lineChild.Visibility != Visibility.Collapsed)
{
lineChildOffset += spacing;
}
}
}
}
}