using System; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; namespace Wpf.Ui.Extend.Layouts; /// /// 堆叠面板 /// public class StackPanelEx : Panel { /// /// 设置某子项附加属性,控制其是否填充到当前StackPanel所在的面板(控制单个子项的在面板填充方式) /// public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached( "Fill", typeof(FillType), typeof(StackPanelEx), new FrameworkPropertyMetadata( FillType.Auto, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsParentArrange | FrameworkPropertyMetadataOptions.AffectsParentMeasure ) ); public static readonly DependencyProperty SpacingProperty = DependencyProperty.Register( nameof(Spacing), typeof(double), typeof(StackPanelEx), new FrameworkPropertyMetadata( 0.0, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure ) ); public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( nameof(Orientation), typeof(Orientation), typeof(StackPanelEx), new FrameworkPropertyMetadata( Orientation.Vertical, FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure ) ); static double CalculateTotalMarginToAdd(UIElementCollection children, double marginBetweenChildren) { //可见的子项数量 var visibleChildrenCount = children .OfType() .Count(x => x.Visibility != Visibility.Collapsed && GetFill(x) != FillType.Ignored); var marginMultiplier = Math.Max(visibleChildrenCount - 1, 0); var totalMarginToAdd = marginBetweenChildren * marginMultiplier; return totalMarginToAdd; } protected override Size ArrangeOverride(Size arrangeSize) { var children = InternalChildren; var totalChildrenCount = children.Count; double accumulatedLeft = 0; double accumulatedTop = 0; var isHorizontal = Orientation == Orientation.Horizontal; var spacing = Spacing; var totalMarginToAdd = CalculateTotalMarginToAdd(children, spacing); var allAutoSizedSum = 0.0; var countOfFillTypes = 0; foreach (var child in children.OfType()) { var fillType = GetFill(child); if (fillType != FillType.Auto) { if (child.Visibility != Visibility.Collapsed && fillType != FillType.Ignored) countOfFillTypes += 1; } else { var desiredSize = isHorizontal ? child.DesiredSize.Width : child.DesiredSize.Height; allAutoSizedSum += desiredSize; } } var remainingForFillTypes = isHorizontal ? Math.Max(0, arrangeSize.Width - allAutoSizedSum - totalMarginToAdd) : Math.Max(0, arrangeSize.Height - allAutoSizedSum - totalMarginToAdd); var fillTypeSize = remainingForFillTypes / countOfFillTypes; for (var i = 0; i < totalChildrenCount; ++i) { var child = children[i]; if (child == null) { continue; } var childDesiredSize = child.DesiredSize; var fillType = GetFill(child); var isCollapsed = child.Visibility == Visibility.Collapsed || fillType == FillType.Ignored; var isLastChild = i == totalChildrenCount - 1; var marginToAdd = isLastChild || isCollapsed ? 0 : spacing; var rcChild = new Rect( accumulatedLeft, accumulatedTop, Math.Max(0.0, arrangeSize.Width - accumulatedLeft), Math.Max(0.0, arrangeSize.Height - accumulatedTop) ); if (isHorizontal) { rcChild.Width = fillType == FillType.Auto || isCollapsed ? childDesiredSize.Width : fillTypeSize; rcChild.Height = arrangeSize.Height; accumulatedLeft += rcChild.Width + marginToAdd; } else { rcChild.Width = arrangeSize.Width; rcChild.Height = fillType == FillType.Auto || isCollapsed ? childDesiredSize.Height : fillTypeSize; accumulatedTop += rcChild.Height + marginToAdd; } child.Arrange(rcChild); } return arrangeSize; } protected override Size MeasureOverride(Size constraint) { var children = InternalChildren; double parentWidth = 0; double parentHeight = 0; double accumulatedWidth = 0; double accumulatedHeight = 0; var isHorizontal = Orientation == Orientation.Horizontal; var totalMarginToAdd = CalculateTotalMarginToAdd(children, Spacing); for (var i = 0; i < children.Count; i++) { var child = children[i]; if (child == null) { continue; } // 先处理Auto的情况,再计算Fill的剩余空间 if (GetFill(child) != FillType.Auto) { continue; } // 子项约束为剩余大小; 即总大小减去前一个子项目消耗的大小。 var childConstraint = new Size( Math.Max(0.0, constraint.Width - accumulatedWidth), Math.Max(0.0, constraint.Height - accumulatedHeight) ); // 计算子项 child.Measure(childConstraint); var childDesiredSize = child.DesiredSize; if (isHorizontal) { accumulatedWidth += childDesiredSize.Width; parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height); } else { parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width); accumulatedHeight += childDesiredSize.Height; } } // 在计算剩余空间之前,将所有边距添加到累积大小中,以便填充元素。 if (isHorizontal) { accumulatedWidth += totalMarginToAdd; } else { accumulatedHeight += totalMarginToAdd; } //Fill类型的总数量 var totalCountOfFillTypes = children .OfType() .Count(x => GetFill(x) == FillType.Fill && x.Visibility != Visibility.Collapsed); //可用的剩余空间 var availableSpaceRemaining = isHorizontal ? Math.Max(0, constraint.Width - accumulatedWidth) : Math.Max(0, constraint.Height - accumulatedHeight); var eachFillTypeSize = totalCountOfFillTypes > 0 ? availableSpaceRemaining / totalCountOfFillTypes : 0; for (var i = 0; i < children.Count; i++) { var child = children[i]; if (child == null) { continue; } // 处理所有的填充,给它们一部分剩余空间 if (GetFill(child) != FillType.Fill) { continue; } // 子项限制是剩余大小;即总大小减去前一个子项限制所消耗的大小。 var childConstraint = isHorizontal ? new Size(eachFillTypeSize, Math.Max(0.0, constraint.Height - accumulatedHeight)) : new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), eachFillTypeSize); // 测量子项. child.Measure(childConstraint); var childDesiredSize = child.DesiredSize; if (isHorizontal) { accumulatedWidth += childDesiredSize.Width; parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height); } else { parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width); accumulatedHeight += childDesiredSize.Height; } } // 确保最终累积的大小反映在 parentSize 中。 parentWidth = Math.Max(parentWidth, accumulatedWidth); parentHeight = Math.Max(parentHeight, accumulatedHeight); var parent = new Size(parentWidth, parentHeight); return parent; } public static FillType GetFill(DependencyObject element) { return (FillType)element.GetValue(FillProperty); } public static void SetFill(DependencyObject element, FillType value) { element.SetValue(FillProperty, value); } /// /// 子项间距 /// public double Spacing { get { return (double)GetValue(SpacingProperty); } set { SetValue(SpacingProperty, value); } } /// /// 堆叠方向 /// public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } } public enum FillType { [Description("实际大小填充")] Auto, [Description("填充剩余空间相当于Stretch")] Fill, [Description("忽略当前项")] Ignored }