236 lines
10 KiB
C#
236 lines
10 KiB
C#
namespace Melskin.Layout
|
||
{
|
||
/// <summary>
|
||
/// 一个自定义布局面板,可将其子元素排列在网格中。
|
||
/// 关键功能: 可将最后一行的子元素进行水平拉伸,以填满整行。
|
||
/// </summary>
|
||
public class UniformGrid : Panel
|
||
{
|
||
#region Dependency Properties
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的列数属性的依赖属性。此属性用于定义网格中的列数,必须大于0,默认值为1。
|
||
/// </summary>
|
||
public static readonly DependencyProperty ColumnsProperty =
|
||
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
||
/// <summary>
|
||
/// 获取或设置网格的列数。必须大于0。默认为1。
|
||
/// </summary>
|
||
public int Columns
|
||
{
|
||
get => (int)GetValue(ColumnsProperty);
|
||
set => SetValue(ColumnsProperty, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的行数属性的依赖属性。此属性用于定义网格中的行数,可以设置为0以自动计算行数,默认值为0。
|
||
/// </summary>
|
||
public static readonly DependencyProperty RowsProperty =
|
||
DependencyProperty.Register(nameof(Rows), typeof(int), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
||
/// <summary>
|
||
/// 获取或设置网格的行数。
|
||
/// 如果为0(默认),行数将根据子元素数量和列数自动计算。
|
||
/// </summary>
|
||
public int Rows
|
||
{
|
||
get => (int)GetValue(RowsProperty);
|
||
set => SetValue(RowsProperty, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的列间距属性的依赖属性。此属性用于定义网格中每列之间的间距,类型为double,默认值为0.0。
|
||
/// </summary>
|
||
public static readonly DependencyProperty ColumnSpacingProperty =
|
||
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的列间距属性。此属性用于定义网格中每列之间的间距,值为double类型,默认值为0.0。
|
||
/// </summary>
|
||
public double ColumnSpacing
|
||
{
|
||
get => (double)GetValue(ColumnSpacingProperty);
|
||
set => SetValue(ColumnSpacingProperty, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的行间距属性的依赖属性。此属性用于定义网格中各行之间的间距,默认值为0。
|
||
/// </summary>
|
||
public static readonly DependencyProperty RowSpacingProperty =
|
||
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的行间距属性。此属性用于定义网格中各行之间的间距,默认值为0.0。
|
||
/// </summary>
|
||
public double RowSpacing
|
||
{
|
||
get => (double)GetValue(RowSpacingProperty);
|
||
set => SetValue(RowSpacingProperty, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的最后一行是否填充属性的依赖属性。此属性用于决定当最后一行未被完全填满时,是否拉伸子元素以填满整行,默认值为true。
|
||
/// </summary>
|
||
public static readonly DependencyProperty IsFillLastRowProperty =
|
||
DependencyProperty.Register(nameof(IsFillLastRow), typeof(bool), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsArrange));
|
||
|
||
/// <summary>
|
||
/// 获取或设置是否水平拉伸最后一行的子元素,以填满该行的全部宽度。
|
||
/// 例如,一个3列的网格有5个元素,当此属性为 true 时,最后两个元素会自动变宽以填满3个列的空间。
|
||
/// </summary>
|
||
public bool IsFillLastRow
|
||
{
|
||
get => (bool)GetValue(IsFillLastRowProperty);
|
||
set => SetValue(IsFillLastRowProperty, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的圆角半径属性的依赖属性。此属性用于定义网格边角的圆角大小,使用CornerRadius结构表示,默认值为默认构造的CornerRadius实例。
|
||
/// </summary>
|
||
public static readonly DependencyProperty CornerRadiusProperty =
|
||
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(UniformGrid),
|
||
new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.AffectsRender));
|
||
|
||
/// <summary>
|
||
/// 表示UniformGrid控件的圆角半径属性。此属性用于定义网格每个角落的圆角大小,可以通过设置不同的值来实现四角的不同圆角效果,默认值为所有角均为0。
|
||
/// </summary>
|
||
public CornerRadius CornerRadius
|
||
{
|
||
get => (CornerRadius)GetValue(CornerRadiusProperty);
|
||
set => SetValue(CornerRadiusProperty, value);
|
||
}
|
||
|
||
#endregion
|
||
|
||
private int computedRows;
|
||
private int computedColumns;
|
||
|
||
/// <inheritdoc/>
|
||
protected override Size MeasureOverride(Size availableSize)
|
||
{
|
||
computedColumns = Math.Max(1, Columns);
|
||
var visibleChildren = InternalChildren.Cast<UIElement>().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);
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
protected override Size ArrangeOverride(Size finalSize)
|
||
{
|
||
var visibleChildren = InternalChildren.Cast<UIElement>().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 (保持不变)
|
||
|
||
/// <summary>
|
||
/// 绘制UniformGrid的视觉效果。
|
||
/// </summary>
|
||
/// <param name="dc">用于绘制的DrawingContext。</param>
|
||
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
|
||
}
|
||
}
|