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 } }