namespace Melskin.Layout { /// /// 定义 FlexibleRowPanel 子元素的默认布局行为。 /// public enum FlexibleRowLayoutMode { /// /// (默认) 子元素默认按比例分配空间 (等效于 Span = 1)。 /// Proportional, /// /// 子元素默认根据其内容自动调整大小 (等效于 Span = -1)。 /// Auto } /// /// 一个全能的单行布局面板,完美支持等分、权重、自动填充以及控件间距。 /// public class FlexibleRowPanel : Panel { #region 1. 控件间距属性 (Spacing) - 【新增】 /// /// 获取或设置子控件之间的间距。 /// public static readonly DependencyProperty SpacingProperty = DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(FlexibleRowPanel), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// 获取或设置子控件之间的间距。 /// 该属性定义了 FlexibleRowPanel 中相邻子控件间的距离。通过调整此值,可以控制布局中各元素的紧凑程度。 /// public double Spacing { get => (double)GetValue(SpacingProperty); set => SetValue(SpacingProperty, value); } #endregion #region 2. 面板模式属性 (LayoutMode) /// /// 获取或设置 FlexibleRowPanel 的布局模式,决定子控件的排列方式。 /// public static readonly DependencyProperty LayoutModeProperty = DependencyProperty.Register(nameof(LayoutMode), typeof(FlexibleRowLayoutMode), typeof(FlexibleRowPanel), new FrameworkPropertyMetadata(FlexibleRowLayoutMode.Proportional, FrameworkPropertyMetadataOptions.AffectsMeasure)); /// /// 获取或设置布局模式,决定子控件在面板中的排列方式。 /// public FlexibleRowLayoutMode LayoutMode { get => (FlexibleRowLayoutMode)GetValue(LayoutModeProperty); set => SetValue(LayoutModeProperty, value); } #endregion #region 3. 便捷填充属性 (IsFill) /// /// 获取或设置是否允许控件在FlexibleRowPanel中自动填充剩余空间。 /// public static readonly DependencyProperty IsFillProperty = DependencyProperty.RegisterAttached("IsFill", typeof(bool), typeof(FlexibleRowPanel), new FrameworkPropertyMetadata(false, OnIsFillChanged)); /// /// 设置指定控件是否在FlexibleRowPanel中自动填充剩余空间。 /// /// 要设置属性的UI元素。 /// 布尔值,指示是否允许该控件自动填充剩余空间。 public static void SetIsFill(UIElement element, bool value) => element.SetValue(IsFillProperty, value); /// /// 获取指定控件是否在FlexibleRowPanel中自动填充剩余空间的设置。 /// /// 要获取属性的UI元素。 /// 布尔值,指示该控件是否被允许自动填充剩余空间。 public static bool GetIsFill(UIElement element) => (bool)element.GetValue(IsFillProperty); private static void OnIsFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is UIElement element && (bool)e.NewValue) { element.SetValue(SpanProperty, 1.0); } } #endregion #region 4. 核心权重属性 (Span) /// /// 获取或设置控件在FlexibleRowPanel中的跨度。 /// public static readonly DependencyProperty SpanProperty = DependencyProperty.RegisterAttached("Span", typeof(double), typeof(FlexibleRowPanel), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsParentMeasure)); /// /// 设置指定控件在FlexibleRowPanel中的跨度。 /// /// 要设置属性的UI元素。 /// 双精度浮点数,表示该控件在FlexibleRowPanel中的跨度。 public static void SetSpan(UIElement element, double value) => element.SetValue(SpanProperty, value); /// /// 获取控件在FlexibleRowPanel中的跨度。 /// /// 要获取跨度的UI元素。 /// 返回一个double值,表示该控件在FlexibleRowPanel中的跨度。 public static double GetSpan(UIElement element) => (double)element.GetValue(SpanProperty); #endregion private double GetEffectiveSpan(UIElement child) { var localValue = child.ReadLocalValue(SpanProperty); if (localValue != DependencyProperty.UnsetValue) { return (double)localValue; } return LayoutMode switch { FlexibleRowLayoutMode.Auto => -1.0, _ => 1.0, }; } //protected override Size MeasureOverride(Size availableSize) //{ // double spaceTakenByAutoChildren = 0; // double totalSpanForFillChildren = 0; // double maxHeight = 0; // // 【修改】计算所有间距占用的总宽度 // var totalSpacing = Math.Max(0, InternalChildren.Count - 1) * Spacing; // // 【修改】计算真正可用于子控件的宽度 // var effectiveWidth = Math.Max(0, availableSize.Width - totalSpacing); // // 第一遍测量:自动大小的控件 // foreach (UIElement child in InternalChildren) // { // if (child == null) continue; // var span = GetEffectiveSpan(child); // if (span < 0) // { // child.Measure(new Size(double.PositiveInfinity, availableSize.Height)); // spaceTakenByAutoChildren += child.DesiredSize.Width; // } // else // { // totalSpanForFillChildren += span; // } // } // var remainingWidth = Math.Max(0, effectiveWidth - spaceTakenByAutoChildren); // var widthPerSpanUnit = (totalSpanForFillChildren > 0 && !double.IsInfinity(remainingWidth)) ? remainingWidth / totalSpanForFillChildren : 0; // // 第二遍测量:比例填充的控件 // foreach (UIElement child in InternalChildren) // { // if (child == null) continue; // var span = GetEffectiveSpan(child); // if (span >= 0) // { // child.Measure(new Size(widthPerSpanUnit * span, availableSize.Height)); // } // if (child.DesiredSize.Height > maxHeight) // { // maxHeight = child.DesiredSize.Height; // } // } // // 返回的宽度应该是面板请求的总宽度 // return new Size(availableSize.Width, maxHeight); //} /// protected override Size MeasureOverride(Size availableSize) { double totalAutoWidth = 0; double totalSpanUnits = 0; double maxHeight = 0; bool isWidthInfinite = double.IsInfinity(availableSize.Width); var childrenCount = InternalChildren.Count; var totalSpacing = Math.Max(0, childrenCount - 1) * Spacing; // 第一遍测量:收集信息 foreach (UIElement child in InternalChildren) { if (child == null || child.Visibility == Visibility.Collapsed) continue; var span = GetEffectiveSpan(child); if (span < 0 || isWidthInfinite) { // 如果宽度是无穷大,或者子项是Auto, // 我们给它无穷大空间,看看它到底想占多大。 child.Measure(new Size(double.PositiveInfinity, availableSize.Height)); totalAutoWidth += child.DesiredSize.Width; } if (span >= 0) { totalSpanUnits += span; } } double finalWidth = 0; if (isWidthInfinite) { // 情况 A: 宽度无穷大 (例如在 StackPanel 或 ScrollViewer 中) // 此时 Span 的逻辑无法分配“剩余空间”,通常做法是让 Span 项以 DesiredSize 形式并存 // 或者你可以给 Span 项一个默认基础宽度(比如 0,或者它们内容的宽度) // 这里我们把所有子项的 DesiredSize 加起来作为我们需要的总宽度 finalWidth = totalAutoWidth + totalSpacing; // 既然是无穷大,我们需要再次测量那些还没有正确 Measure 的 Span 项(可选) foreach (UIElement child in InternalChildren) { if (child == null || child.Visibility == Visibility.Collapsed) continue; maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } } else { // 情况 B: 宽度是有限的 (正常的 Grid 或固定宽度的容器) double remainingWidth = Math.Max(0, availableSize.Width - totalSpacing - totalAutoWidth); double widthPerSpanUnit = totalSpanUnits > 0 ? remainingWidth / totalSpanUnits : 0; foreach (UIElement child in InternalChildren) { if (child == null || child.Visibility == Visibility.Collapsed) continue; var span = GetEffectiveSpan(child); if (span >= 0) { // 给 Span 项分配计算后的精确宽度 child.Measure(new Size(widthPerSpanUnit * span, availableSize.Height)); } maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } finalWidth = availableSize.Width; // 在有限宽度下,通常占用全部可用宽度 } // 重要:返回计算出的有限尺寸,绝不返回 availableSize.Width (如果是 Infinity) return new Size(finalWidth, maxHeight); } /// protected override Size ArrangeOverride(Size finalSize) { double spaceTakenByAutoChildren = 0; double totalSpanForFillChildren = 0; // 【修改】计算所有间距占用的总宽度 var totalSpacing = Math.Max(0, InternalChildren.Count - 1) * Spacing; // 【修改】计算真正可用于子控件的宽度 var effectiveWidth = Math.Max(0, finalSize.Width - totalSpacing); // 重新计算一遍各项数值,因为 Measure 和 Arrange 的 Size 可能不同 foreach (UIElement child in InternalChildren) { if (child == null) continue; var span = GetEffectiveSpan(child); if (span < 0) { spaceTakenByAutoChildren += child.DesiredSize.Width; } else { totalSpanForFillChildren += span; } } var remainingWidth = Math.Max(0, effectiveWidth - spaceTakenByAutoChildren); var widthPerSpanUnit = (totalSpanForFillChildren > 0) ? remainingWidth / totalSpanForFillChildren : 0; double currentX = 0; // 遍历并排列每个子元素 for (var i = 0; i < InternalChildren.Count; i++) { var child = InternalChildren[i]; if (child == null) continue; var span = GetEffectiveSpan(child); var childWidth = (span < 0) ? child.DesiredSize.Width : widthPerSpanUnit * span; child.Arrange(new Rect(currentX, 0, childWidth, finalSize.Height)); // 【修改】移动 currentX,并为下一个控件加上间距 currentX += childWidth; if (i < InternalChildren.Count - 1) { currentX += Spacing; } } return finalSize; } } }