Files
ShrlAlgoToolkit/NeuWPF/NeoUI/Layout/FlexibleRowPanel.cs

237 lines
9.5 KiB
C#
Raw Normal View History

2025-08-20 12:10:13 +08:00
namespace NeumUI.Layout
{
/// <summary>
/// 定义 FlexibleRowPanel 子元素的默认布局行为。
/// </summary>
public enum FlexibleRowLayoutMode
{
/// <summary>
/// (默认) 子元素默认按比例分配空间 (等效于 Span = 1)。
/// </summary>
Proportional,
/// <summary>
/// 子元素默认根据其内容自动调整大小 (等效于 Span = -1)。
/// </summary>
Auto
}
/// <summary>
/// 一个全能的单行布局面板,完美支持等分、权重、自动填充以及控件间距。
/// </summary>
public class FlexibleRowPanel : Panel
{
#region 1. (Spacing) -
/// <summary>
/// 获取或设置子控件之间的间距。
/// </summary>
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register(nameof(Spacing), typeof(double), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置子控件之间的间距。
/// 该属性定义了 FlexibleRowPanel 中相邻子控件间的距离。通过调整此值,可以控制布局中各元素的紧凑程度。
/// </summary>
public double Spacing
{
get => (double)GetValue(SpacingProperty);
set => SetValue(SpacingProperty, value);
}
#endregion
#region 2. (LayoutMode)
/// <summary>
/// 获取或设置 FlexibleRowPanel 的布局模式,决定子控件的排列方式。
/// </summary>
public static readonly DependencyProperty LayoutModeProperty =
DependencyProperty.Register(nameof(LayoutMode), typeof(FlexibleRowLayoutMode), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(FlexibleRowLayoutMode.Proportional, FrameworkPropertyMetadataOptions.AffectsMeasure));
/// <summary>
/// 获取或设置布局模式,决定子控件在面板中的排列方式。
/// </summary>
public FlexibleRowLayoutMode LayoutMode
{
get => (FlexibleRowLayoutMode)GetValue(LayoutModeProperty);
set => SetValue(LayoutModeProperty, value);
}
#endregion
#region 3. 便 (IsFill)
/// <summary>
/// 获取或设置是否允许控件在FlexibleRowPanel中自动填充剩余空间。
/// </summary>
public static readonly DependencyProperty IsFillProperty =
DependencyProperty.RegisterAttached("IsFill", typeof(bool), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(false, OnIsFillChanged));
/// <summary>
/// 设置指定控件是否在FlexibleRowPanel中自动填充剩余空间。
/// </summary>
/// <param name="element">要设置属性的UI元素。</param>
/// <param name="value">布尔值,指示是否允许该控件自动填充剩余空间。</param>
public static void SetIsFill(UIElement element, bool value) => element.SetValue(IsFillProperty, value);
/// <summary>
/// 获取指定控件是否在FlexibleRowPanel中自动填充剩余空间的设置。
/// </summary>
/// <param name="element">要获取属性的UI元素。</param>
/// <returns>布尔值,指示该控件是否被允许自动填充剩余空间。</returns>
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)
/// <summary>
/// 获取或设置控件在FlexibleRowPanel中的跨度。
/// </summary>
public static readonly DependencyProperty SpanProperty =
DependencyProperty.RegisterAttached("Span", typeof(double), typeof(FlexibleRowPanel),
new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsParentMeasure));
/// <summary>
/// 设置指定控件在FlexibleRowPanel中的跨度。
/// </summary>
/// <param name="element">要设置属性的UI元素。</param>
/// <param name="value">双精度浮点数表示该控件在FlexibleRowPanel中的跨度。</param>
public static void SetSpan(UIElement element, double value) => element.SetValue(SpanProperty, value);
/// <summary>
/// 获取控件在FlexibleRowPanel中的跨度。
/// </summary>
/// <param name="element">要获取跨度的UI元素。</param>
/// <returns>返回一个double值表示该控件在FlexibleRowPanel中的跨度。</returns>
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) ---
/// <inheritdoc />
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);
}
/// <inheritdoc />
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;
}
}
}