功能更新
This commit is contained in:
235
Melskin/Layout/UniformGrid.cs
Normal file
235
Melskin/Layout/UniformGrid.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user