200 lines
7.1 KiB
C#
200 lines
7.1 KiB
C#
/* This file is generated by DeepSeek */
|
|||
|
|
|
||
|
|
using System;
|
||
|
|
using System.Windows;
|
||
|
|
using System.Windows.Controls;
|
||
|
|
|
||
|
|
namespace PCL.Core.UI.Controls;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 瀑布流布局模式
|
||
|
|
/// </summary>
|
||
|
|
public enum WaterfallLayoutMode
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// 固定列数模式,使用 ColumnCount 属性指定列数
|
||
|
|
/// </summary>
|
||
|
|
FixedColumns,
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 自适应模式,根据可用宽度和 MaxItemWidth 自动计算列数
|
||
|
|
/// </summary>
|
||
|
|
AutoFit
|
||
|
|
}
|
||
|
|
|
||
|
|
public class WaterfallPanel : Panel
|
||
|
|
{
|
||
|
|
// 私有字段,存储 Measure 阶段计算出的实际列数(用于 Arrange 阶段)
|
||
|
|
private int _computedColumnCount;
|
||
|
|
|
||
|
|
#region 依赖属性
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 布局模式(固定列数 / 自适应)
|
||
|
|
/// </summary>
|
||
|
|
public WaterfallLayoutMode LayoutMode
|
||
|
|
{
|
||
|
|
get { return (WaterfallLayoutMode)GetValue(LayoutModeProperty); }
|
||
|
|
set { SetValue(LayoutModeProperty, value); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static readonly DependencyProperty LayoutModeProperty =
|
||
|
|
DependencyProperty.Register(nameof(LayoutMode), typeof(WaterfallLayoutMode), typeof(WaterfallPanel),
|
||
|
|
new FrameworkPropertyMetadata(WaterfallLayoutMode.FixedColumns, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 固定列数(仅在 LayoutMode = FixedColumns 时生效)
|
||
|
|
/// </summary>
|
||
|
|
public int ColumnCount
|
||
|
|
{
|
||
|
|
get { return (int)GetValue(ColumnCountProperty); }
|
||
|
|
set { SetValue(ColumnCountProperty, value); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static readonly DependencyProperty ColumnCountProperty =
|
||
|
|
DependencyProperty.Register(nameof(ColumnCount), typeof(int), typeof(WaterfallPanel),
|
||
|
|
new FrameworkPropertyMetadata(2, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 列间距
|
||
|
|
/// </summary>
|
||
|
|
public double ColumnSpacing
|
||
|
|
{
|
||
|
|
get { return (double)GetValue(ColumnSpacingProperty); }
|
||
|
|
set { SetValue(ColumnSpacingProperty, value); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static readonly DependencyProperty ColumnSpacingProperty =
|
||
|
|
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(WaterfallPanel),
|
||
|
|
new FrameworkPropertyMetadata(5.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 行间距
|
||
|
|
/// </summary>
|
||
|
|
public double RowSpacing
|
||
|
|
{
|
||
|
|
get { return (double)GetValue(RowSpacingProperty); }
|
||
|
|
set { SetValue(RowSpacingProperty, value); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static readonly DependencyProperty RowSpacingProperty =
|
||
|
|
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(WaterfallPanel),
|
||
|
|
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 项的最大宽度(仅在 LayoutMode = AutoFit 时生效)
|
||
|
|
/// </summary>
|
||
|
|
public double MaxItemWidth
|
||
|
|
{
|
||
|
|
get { return (double)GetValue(MaxItemWidthProperty); }
|
||
|
|
set { SetValue(MaxItemWidthProperty, value); }
|
||
|
|
}
|
||
|
|
|
||
|
|
public static readonly DependencyProperty MaxItemWidthProperty =
|
||
|
|
DependencyProperty.Register(nameof(MaxItemWidth), typeof(double), typeof(WaterfallPanel),
|
||
|
|
new FrameworkPropertyMetadata(100.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
|
|
||
|
|
#endregion
|
||
|
|
|
||
|
|
protected override Size MeasureOverride(Size availableSize)
|
||
|
|
{
|
||
|
|
// 1. 确定列数
|
||
|
|
int columnCount;
|
||
|
|
if (LayoutMode == WaterfallLayoutMode.FixedColumns)
|
||
|
|
{
|
||
|
|
columnCount = ColumnCount;
|
||
|
|
}
|
||
|
|
else // AutoFit
|
||
|
|
{
|
||
|
|
if (double.IsInfinity(availableSize.Width) || double.IsNaN(availableSize.Width))
|
||
|
|
{
|
||
|
|
// 宽度无限时,回退到固定列数模式(使用 ColumnCount 作为后备)
|
||
|
|
columnCount = ColumnCount;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
double effectiveMaxWidth = MaxItemWidth > 0 ? MaxItemWidth : 100; // 防御性处理
|
||
|
|
double slotWidth = effectiveMaxWidth + ColumnSpacing;
|
||
|
|
columnCount = Math.Max(1, (int)Math.Floor((availableSize.Width + ColumnSpacing) / slotWidth));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_computedColumnCount = columnCount;
|
||
|
|
|
||
|
|
// 2. 计算每列的实际宽度
|
||
|
|
double colWidth = (availableSize.Width - (columnCount - 1) * ColumnSpacing) / columnCount;
|
||
|
|
colWidth = Math.Max(0, colWidth); // 避免负宽度
|
||
|
|
|
||
|
|
// 3. 记录每列的当前高度(用于测量时决定元素放入哪一列)
|
||
|
|
double[] colHeights = new double[columnCount];
|
||
|
|
|
||
|
|
// 4. 测量所有子元素
|
||
|
|
foreach (UIElement child in InternalChildren)
|
||
|
|
{
|
||
|
|
// 约束子元素宽度为列宽,高度不限
|
||
|
|
child.Measure(new Size(colWidth, double.PositiveInfinity));
|
||
|
|
Size desiredSize = child.DesiredSize;
|
||
|
|
|
||
|
|
// 找到当前高度最小的列
|
||
|
|
int minColIndex = _GetMinHeightColumnIndex(colHeights);
|
||
|
|
double y = colHeights[minColIndex]; // 当前列已占高度,即新元素的 Y 坐标
|
||
|
|
|
||
|
|
// 更新该列高度:加上元素高度和行间距(为下一个元素预留)
|
||
|
|
colHeights[minColIndex] += desiredSize.Height + RowSpacing;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 5. 计算面板所需总高度(取最高列的实际内容高度,需减去最后一个多余的行间距)
|
||
|
|
double totalHeight = 0;
|
||
|
|
for (int i = 0; i < colHeights.Length; i++)
|
||
|
|
{
|
||
|
|
if (colHeights[i] > 0)
|
||
|
|
totalHeight = Math.Max(totalHeight, colHeights[i] - RowSpacing);
|
||
|
|
else
|
||
|
|
totalHeight = Math.Max(totalHeight, colHeights[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return new Size(availableSize.Width, totalHeight);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override Size ArrangeOverride(Size finalSize)
|
||
|
|
{
|
||
|
|
if (_computedColumnCount <= 0) return finalSize;
|
||
|
|
|
||
|
|
// 使用 Measure 阶段确定的列数,但根据 finalSize 重新计算列宽(保证填满可用宽度)
|
||
|
|
double colWidth = (finalSize.Width - (_computedColumnCount - 1) * ColumnSpacing) / _computedColumnCount;
|
||
|
|
colWidth = Math.Max(0, colWidth);
|
||
|
|
|
||
|
|
double[] colHeights = new double[_computedColumnCount];
|
||
|
|
|
||
|
|
foreach (UIElement child in InternalChildren)
|
||
|
|
{
|
||
|
|
int colIndex = _GetMinHeightColumnIndex(colHeights);
|
||
|
|
double x = colIndex * (colWidth + ColumnSpacing);
|
||
|
|
double y = colHeights[colIndex];
|
||
|
|
|
||
|
|
child.Arrange(new Rect(new Point(x, y), new Size(colWidth, child.DesiredSize.Height)));
|
||
|
|
|
||
|
|
// 更新该列高度:加上元素高度和行间距(为下一个元素定位)
|
||
|
|
colHeights[colIndex] += child.DesiredSize.Height + RowSpacing;
|
||
|
|
}
|
||
|
|
|
||
|
|
return finalSize;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取当前高度最小的列索引
|
||
|
|
/// </summary>
|
||
|
|
private int _GetMinHeightColumnIndex(double[] colHeights)
|
||
|
|
{
|
||
|
|
int minIndex = 0;
|
||
|
|
double minHeight = colHeights[0];
|
||
|
|
for (int i = 1; i < colHeights.Length; i++)
|
||
|
|
{
|
||
|
|
if (colHeights[i] < minHeight)
|
||
|
|
{
|
||
|
|
minHeight = colHeights[i];
|
||
|
|
minIndex = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return minIndex;
|
||
|
|
}
|
||
|
|
}
|