namespace NeoUI.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, }; } // --- 核心布局逻辑 (已更新以支持 Spacing) --- /// 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 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; } } }