namespace Melskin.Layout
{
///
/// 一个自定义布局面板,可将其子元素排列在网格中。
/// 关键功能: 可将最后一行的子元素进行水平拉伸,以填满整行。
///
public class UniformGrid : Panel
{
#region Dependency Properties
///
/// 表示UniformGrid控件的列数属性的依赖属性。此属性用于定义网格中的列数,必须大于0,默认值为1。
///
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(UniformGrid),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
///
/// 获取或设置网格的列数。必须大于0。默认为1。
///
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
///
/// 表示UniformGrid控件的行数属性的依赖属性。此属性用于定义网格中的行数,可以设置为0以自动计算行数,默认值为0。
///
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(nameof(Rows), typeof(int), typeof(UniformGrid),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure));
///
/// 获取或设置网格的行数。
/// 如果为0(默认),行数将根据子元素数量和列数自动计算。
///
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
///
/// 表示UniformGrid控件的列间距属性的依赖属性。此属性用于定义网格中每列之间的间距,类型为double,默认值为0.0。
///
public static readonly DependencyProperty ColumnSpacingProperty =
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
///
/// 表示UniformGrid控件的列间距属性。此属性用于定义网格中每列之间的间距,值为double类型,默认值为0.0。
///
public double ColumnSpacing
{
get => (double)GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}
///
/// 表示UniformGrid控件的行间距属性的依赖属性。此属性用于定义网格中各行之间的间距,默认值为0。
///
public static readonly DependencyProperty RowSpacingProperty =
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
///
/// 表示UniformGrid控件的行间距属性。此属性用于定义网格中各行之间的间距,默认值为0.0。
///
public double RowSpacing
{
get => (double)GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}
///
/// 表示UniformGrid控件的最后一行是否填充属性的依赖属性。此属性用于决定当最后一行未被完全填满时,是否拉伸子元素以填满整行,默认值为true。
///
public static readonly DependencyProperty IsFillLastRowProperty =
DependencyProperty.Register(nameof(IsFillLastRow), typeof(bool), typeof(UniformGrid),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange));
///
/// 获取或设置是否水平拉伸最后一行的子元素,以填满该行的全部宽度。
/// 例如,一个3列的网格有5个元素,当此属性为 true 时,最后两个元素会自动变宽以填满3个列的空间。
///
public bool IsFillLastRow
{
get => (bool)GetValue(IsFillLastRowProperty);
set => SetValue(IsFillLastRowProperty, value);
}
///
/// 表示UniformGrid控件的圆角半径属性的依赖属性。此属性用于定义网格边角的圆角大小,使用CornerRadius结构表示,默认值为默认构造的CornerRadius实例。
///
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(UniformGrid),
new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.AffectsRender));
///
/// 表示UniformGrid控件的圆角半径属性。此属性用于定义网格每个角落的圆角大小,可以通过设置不同的值来实现四角的不同圆角效果,默认值为所有角均为0。
///
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
#endregion
private int computedRows;
private int computedColumns;
///
protected override Size MeasureOverride(Size availableSize)
{
computedColumns = Math.Max(1, Columns);
var visibleChildren = InternalChildren.Cast().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (Rows > 0)
{
computedRows = Rows;
}
else
{
computedRows = (visibleChildren.Count + computedColumns - 1) / computedColumns;
if (computedRows == 0) computedRows = 1;
}
double maxChildWidth = 0;
double maxChildHeight = 0;
// 所有子元素共享相同的可用尺寸
foreach (var child in visibleChildren)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
maxChildWidth = Math.Max(maxChildWidth, child.DesiredSize.Width);
maxChildHeight = Math.Max(maxChildHeight, child.DesiredSize.Height);
}
var totalWidth = maxChildWidth * computedColumns + ColumnSpacing * (computedColumns - 1);
var totalHeight = maxChildHeight * computedRows + RowSpacing * (computedRows - 1);
return new Size(totalWidth, totalHeight);
}
///
protected override Size ArrangeOverride(Size finalSize)
{
var visibleChildren = InternalChildren.Cast().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (visibleChildren.Count == 0) return finalSize;
var cellWidth = Math.Max(0, (finalSize.Width - (computedColumns - 1) * ColumnSpacing) / computedColumns);
var cellHeight = Math.Max(0, (finalSize.Height - (computedRows - 1) * RowSpacing) / computedRows);
var itemsInFullRows = (computedRows - 1) * computedColumns;
var lastRowItemCount = visibleChildren.Count - itemsInFullRows;
var lastRowCellWidth = cellWidth;
if (IsFillLastRow && lastRowItemCount > 0)
{
// **核心逻辑**: 为最后一行重新计算单元格宽度
lastRowCellWidth = (finalSize.Width - (lastRowItemCount - 1) * ColumnSpacing) / lastRowItemCount;
}
for (var i = 0; i < visibleChildren.Count; i++)
{
var child = visibleChildren[i];
var row = i / computedColumns;
var col = i % computedColumns;
var currentCellWidth = (row == computedRows - 1) ? lastRowCellWidth : cellWidth;
// 如果是最后一行,x坐标的计算需要基于该行内的索引和新的单元格宽度
double x;
if (row == computedRows - 1 && IsFillLastRow)
{
var colInLastRow = i - itemsInFullRows;
x = colInLastRow * (currentCellWidth + ColumnSpacing);
}
else
{
x = col * (cellWidth + ColumnSpacing);
}
var y = row * (cellHeight + RowSpacing);
child.Arrange(new Rect(x, y, currentCellWidth, cellHeight));
}
return finalSize;
}
#region Clipping Logic (保持不变)
///
/// 绘制UniformGrid的视觉效果。
///
/// 用于绘制的DrawingContext。
protected override void OnRender(DrawingContext dc)
{
ApplyClip();
base.OnRender(dc);
}
private static bool IsZero(double value) => Math.Abs(value) < 1.53E-06;
private void ApplyClip()
{
var cr = CornerRadius;
if (IsZero(cr.TopLeft) && IsZero(cr.TopRight) &&
IsZero(cr.BottomLeft) && IsZero(cr.BottomRight))
{
this.Clip = null;
return;
}
var clipGeometry = new RectangleGeometry(new Rect(this.RenderSize), cr.TopLeft, cr.TopLeft);
if (cr.TopLeft == cr.TopRight && cr.TopLeft == cr.BottomLeft && cr.TopLeft == cr.BottomRight)
{
clipGeometry.RadiusX = cr.TopLeft;
clipGeometry.RadiusY = cr.TopLeft;
}
else
{
clipGeometry.RadiusX = (cr.TopLeft + cr.TopRight) / 2;
clipGeometry.RadiusY = (cr.TopLeft + cr.BottomLeft) / 2;
}
clipGeometry.Freeze();
this.Clip = clipGeometry;
}
#endregion
}
}