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