Files
Shrlalgo.RvKits/NeoUI/Melskin/Layout/UniformGrid.cs

236 lines
10 KiB
C#
Raw Normal View History

2026-01-02 17:30:30 +08:00
namespace VariaStudio.Layout
2025-08-12 23:08:54 +08:00
{
/// <summary>
/// 一个自定义布局面板,可将其子元素排列在网格中。
/// 关键功能: 可将最后一行的子元素进行水平拉伸,以填满整行。
/// </summary>
public class UniformGrid : Panel
{
#region Dependency Properties
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的列数属性的依赖属性。此属性用于定义网格中的列数必须大于0默认值为1。
/// </summary>
2025-08-12 23:08:54 +08:00
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);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的行数属性的依赖属性。此属性用于定义网格中的行数可以设置为0以自动计算行数默认值为0。
/// </summary>
2025-08-12 23:08:54 +08:00
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);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的列间距属性的依赖属性。此属性用于定义网格中每列之间的间距类型为double默认值为0.0。
/// </summary>
2025-08-12 23:08:54 +08:00
public static readonly DependencyProperty ColumnSpacingProperty =
DependencyProperty.Register(nameof(ColumnSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的列间距属性。此属性用于定义网格中每列之间的间距值为double类型默认值为0.0。
/// </summary>
2025-08-12 23:08:54 +08:00
public double ColumnSpacing
{
get => (double)GetValue(ColumnSpacingProperty);
set => SetValue(ColumnSpacingProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的行间距属性的依赖属性。此属性用于定义网格中各行之间的间距默认值为0。
/// </summary>
2025-08-12 23:08:54 +08:00
public static readonly DependencyProperty RowSpacingProperty =
DependencyProperty.Register(nameof(RowSpacing), typeof(double), typeof(UniformGrid),
new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的行间距属性。此属性用于定义网格中各行之间的间距默认值为0.0。
/// </summary>
2025-08-12 23:08:54 +08:00
public double RowSpacing
{
get => (double)GetValue(RowSpacingProperty);
set => SetValue(RowSpacingProperty, value);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的最后一行是否填充属性的依赖属性。此属性用于决定当最后一行未被完全填满时是否拉伸子元素以填满整行默认值为true。
/// </summary>
2025-08-12 23:08:54 +08:00
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);
}
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的圆角半径属性的依赖属性。此属性用于定义网格边角的圆角大小使用CornerRadius结构表示默认值为默认构造的CornerRadius实例。
/// </summary>
2025-08-12 23:08:54 +08:00
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(UniformGrid),
new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.AffectsRender));
2025-08-20 12:10:13 +08:00
/// <summary>
/// 表示UniformGrid控件的圆角半径属性。此属性用于定义网格每个角落的圆角大小可以通过设置不同的值来实现四角的不同圆角效果默认值为所有角均为0。
/// </summary>
2025-08-12 23:08:54 +08:00
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
#endregion
2025-08-20 12:10:13 +08:00
private int computedRows;
private int computedColumns;
2025-08-12 23:08:54 +08:00
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
2025-08-20 12:10:13 +08:00
computedColumns = Math.Max(1, Columns);
2025-08-12 23:08:54 +08:00
var visibleChildren = InternalChildren.Cast<UIElement>().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (Rows > 0)
{
2025-08-20 12:10:13 +08:00
computedRows = Rows;
2025-08-12 23:08:54 +08:00
}
else
{
2025-08-20 12:10:13 +08:00
computedRows = (visibleChildren.Count + computedColumns - 1) / computedColumns;
if (computedRows == 0) computedRows = 1;
2025-08-12 23:08:54 +08:00
}
double maxChildWidth = 0;
double maxChildHeight = 0;
// 所有子元素共享相同的可用尺寸
2025-08-20 12:10:13 +08:00
foreach (var child in visibleChildren)
2025-08-12 23:08:54 +08:00
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
maxChildWidth = Math.Max(maxChildWidth, child.DesiredSize.Width);
maxChildHeight = Math.Max(maxChildHeight, child.DesiredSize.Height);
}
2025-08-20 12:10:13 +08:00
var totalWidth = maxChildWidth * computedColumns + ColumnSpacing * (computedColumns - 1);
var totalHeight = maxChildHeight * computedRows + RowSpacing * (computedRows - 1);
2025-08-12 23:08:54 +08:00
return new Size(totalWidth, totalHeight);
}
2025-08-20 12:10:13 +08:00
/// <inheritdoc />
2025-08-12 23:08:54 +08:00
protected override Size ArrangeOverride(Size finalSize)
{
var visibleChildren = InternalChildren.Cast<UIElement>().Where(c => c.Visibility != Visibility.Collapsed).ToList();
if (visibleChildren.Count == 0) return finalSize;
2025-08-20 12:10:13 +08:00
var cellWidth = Math.Max(0, (finalSize.Width - (computedColumns - 1) * ColumnSpacing) / computedColumns);
var cellHeight = Math.Max(0, (finalSize.Height - (computedRows - 1) * RowSpacing) / computedRows);
2025-08-12 23:08:54 +08:00
2025-08-20 12:10:13 +08:00
var itemsInFullRows = (computedRows - 1) * computedColumns;
var lastRowItemCount = visibleChildren.Count - itemsInFullRows;
2025-08-12 23:08:54 +08:00
2025-08-20 12:10:13 +08:00
var lastRowCellWidth = cellWidth;
2025-08-12 23:08:54 +08:00
if (IsFillLastRow && lastRowItemCount > 0)
{
// **核心逻辑**: 为最后一行重新计算单元格宽度
lastRowCellWidth = (finalSize.Width - (lastRowItemCount - 1) * ColumnSpacing) / lastRowItemCount;
}
2025-08-20 12:10:13 +08:00
for (var i = 0; i < visibleChildren.Count; i++)
2025-08-12 23:08:54 +08:00
{
var child = visibleChildren[i];
2025-08-20 12:10:13 +08:00
var row = i / computedColumns;
var col = i % computedColumns;
2025-08-12 23:08:54 +08:00
2025-08-20 12:10:13 +08:00
var currentCellWidth = (row == computedRows - 1) ? lastRowCellWidth : cellWidth;
2025-08-12 23:08:54 +08:00
// 如果是最后一行x坐标的计算需要基于该行内的索引和新的单元格宽度
double x;
2025-08-20 12:10:13 +08:00
if (row == computedRows - 1 && IsFillLastRow)
2025-08-12 23:08:54 +08:00
{
2025-08-20 12:10:13 +08:00
var colInLastRow = i - itemsInFullRows;
2025-08-12 23:08:54 +08:00
x = colInLastRow * (currentCellWidth + ColumnSpacing);
}
else
{
x = col * (cellWidth + ColumnSpacing);
}
2025-08-20 12:10:13 +08:00
var y = row * (cellHeight + RowSpacing);
2025-08-12 23:08:54 +08:00
child.Arrange(new Rect(x, y, currentCellWidth, cellHeight));
}
return finalSize;
}
#region Clipping Logic ()
2025-08-20 12:10:13 +08:00
/// <summary>
/// 绘制UniformGrid的视觉效果。
/// </summary>
/// <param name="dc">用于绘制的DrawingContext。</param>
2025-08-12 23:08:54 +08:00
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
}
}