在 WPF 开发中,原生Grid控件是布局的核心工具,但它存在一个明显痛点:需要手动定义RowDefinitions和ColumnDefinitions,且每个子控件都要显式设置Grid.Row和Grid.Column属性,当子控件数量变化或布局调整时,维护成本较高。
本文将介绍一款自定义控件AutoGrid,它基于原生Grid扩展而来,支持自动生成行列 、自动排列子控件 、统一配置子控件样式,大幅提升网格布局的开发效率和灵活性。
一、AutoGrid 核心功能概述
AutoGrid的核心价值是简化Grid布局的配置流程,主要提供以下功能:
- 支持两种方式定义行列:直接设置行列数量(
ColumnCount/RowCount),或通过字符串格式自定义行列尺寸(Columns/Rows,支持*、像素、Auto); - 支持两种布局方向(
Orientation):Horizontal(先行后列,横向优先填充)、Vertical(先列后行,纵向优先填充); - 统一配置子控件样式:批量设置子控件的边距(
ChildMargin)、水平对齐(ChildHorizontalAlignment)、垂直对齐(ChildVerticalAlignment),且支持单个子控件覆盖默认样式; - 支持子控件
RowSpan/ColumnSpan,自动标记被占用的格子,避免子控件重叠; - 可禁用自动索引(
IsAutoIndexing),兼容原生Grid的手动布局方式。
二、完整代码实现
AutoGrid的实现包含两个类:核心控件AutoGrid(继承自原生Grid)和依赖属性扩展类DependencyExtensions(提供默认值判断扩展方法),代码如下:
2.1 依赖属性扩展类 DependencyExtensions
cs
namespace WpfAutoGrid
{
using System.Windows;
/// <summary>
/// 依赖对象扩展方法类
/// 提供"仅当属性为默认值时赋值"的功能
/// </summary>
public static class DependencyExtensions
{
/// <summary>
/// 仅当目标依赖属性为默认值(未显式设置)时,才为其赋值
/// </summary>
/// <typeparam name="T">属性值类型</typeparam>
/// <param name="d">依赖对象</param>
/// <param name="property">依赖属性</param>
/// <param name="value">要设置的属性值</param>
/// <returns>是否成功赋值</returns>
public static bool SetIfDefault<T>(this DependencyObject d, DependencyProperty property, T value)
{
// 获取属性值来源,判断是否为默认值
if (DependencyPropertyHelper.GetValueSource(d, property).BaseValueSource == BaseValueSource.Default)
{
d.SetValue(property, value);
return true;
}
return false;
}
}
}
2.2 核心控件 AutoGrid
cs
namespace WpfAutoGrid
{
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
/// <summary>
/// 灵活的自动布局网格控件
/// 基于原生Grid扩展,支持行列自动生成、子控件自动排列
/// 部分实现参考:http://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/
/// </summary>
public class AutoGrid : Grid
{
#region 依赖属性 - 公共配置项
/// <summary>
/// 所有子控件的统一水平对齐方式
/// </summary>
[Category("Layout"), Description("Presets the horizontal alignment of all child controls")]
public HorizontalAlignment? ChildHorizontalAlignment
{
get { return (HorizontalAlignment?)GetValue(ChildHorizontalAlignmentProperty); }
set { SetValue(ChildHorizontalAlignmentProperty, value); }
}
/// <summary>
/// 所有子控件的统一边距
/// </summary>
[Category("Layout"), Description("Presets the margin of all child controls")]
public Thickness? ChildMargin
{
get { return (Thickness?)GetValue(ChildMarginProperty); }
set { SetValue(ChildMarginProperty, value); }
}
/// <summary>
/// 所有子控件的统一垂直对齐方式
/// </summary>
[Category("Layout"), Description("Presets the vertical alignment of all child controls")]
public VerticalAlignment? ChildVerticalAlignment
{
get { return (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty); }
set { SetValue(ChildVerticalAlignmentProperty, value); }
}
/// <summary>
/// 列数量(自动生成列)
/// </summary>
[Category("Layout"), Description("Defines a set number of columns")]
public int ColumnCount
{
get { return (int)GetValue(ColumnCountProperty); }
set { SetValue(ColumnCountProperty, value); }
}
/// <summary>
/// 列定义字符串(逗号分隔,支持*、像素、Auto)
/// </summary>
[Category("Layout"), Description("Defines all columns using comma separated grid length notation")]
public string Columns
{
get { return (string)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
/// <summary>
/// 统一列宽度(针对ColumnCount生成的列)
/// </summary>
[Category("Layout"), Description("Presets the width of all columns set using the ColumnCount property")]
public GridLength ColumnWidth
{
get { return (GridLength)GetValue(ColumnWidthProperty); }
set { SetValue(ColumnWidthProperty, value); }
}
/// <summary>
/// 是否启用子控件自动索引(默认true)
/// </summary>
[Category("Layout"), Description("Set to false to disable the auto layout functionality")]
public bool IsAutoIndexing
{
get { return (bool)GetValue(IsAutoIndexingProperty); }
set { SetValue(IsAutoIndexingProperty, value); }
}
/// <summary>
/// 布局方向(默认Horizontal:先行后列)
/// </summary>
[Category("Layout"), Description("Defines the directionality of the autolayout. Use vertical for a column first layout, horizontal for a row first layout.")]
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// 行数量(自动生成行)
/// </summary>
[Category("Layout"), Description("Defines a set number of rows")]
public int RowCount
{
get { return (int)GetValue(RowCountProperty); }
set { SetValue(RowCountProperty, value); }
}
/// <summary>
/// 统一行高度(针对RowCount生成的行)
/// </summary>
[Category("Layout"), Description("Presets the height of all rows set using the RowCount property")]
public GridLength RowHeight
{
get { return (GridLength)GetValue(RowHeightProperty); }
set { SetValue(RowHeightProperty, value); }
}
/// <summary>
/// 行定义字符串(逗号分隔,支持*、像素、Auto)
/// </summary>
[Category("Layout"), Description("Defines all rows using comma separated grid length notation")]
public string Rows
{
get { return (string)GetValue(RowsProperty); }
set { SetValue(RowsProperty, value); }
}
#endregion
#region 依赖属性 - 注册与回调
public static readonly DependencyProperty ChildHorizontalAlignmentProperty =
DependencyProperty.Register("ChildHorizontalAlignment", typeof(HorizontalAlignment?), typeof(AutoGrid),
new FrameworkPropertyMetadata((HorizontalAlignment?)null, FrameworkPropertyMetadataOptions.AffectsMeasure, OnChildHorizontalAlignmentChanged));
public static readonly DependencyProperty ChildMarginProperty =
DependencyProperty.Register("ChildMargin", typeof(Thickness?), typeof(AutoGrid),
new FrameworkPropertyMetadata((Thickness?)null, FrameworkPropertyMetadataOptions.AffectsMeasure, OnChildMarginChanged));
public static readonly DependencyProperty ChildVerticalAlignmentProperty =
DependencyProperty.Register("ChildVerticalAlignment", typeof(VerticalAlignment?), typeof(AutoGrid),
new FrameworkPropertyMetadata((VerticalAlignment?)null, FrameworkPropertyMetadataOptions.AffectsMeasure, OnChildVerticalAlignmentChanged));
public static readonly DependencyProperty ColumnCountProperty =
DependencyProperty.RegisterAttached("ColumnCount", typeof(int), typeof(AutoGrid),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure, ColumnCountChanged));
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.RegisterAttached("Columns", typeof(string), typeof(AutoGrid),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsMeasure, ColumnsChanged));
public static readonly DependencyProperty ColumnWidthProperty =
DependencyProperty.RegisterAttached("ColumnWidth", typeof(GridLength), typeof(AutoGrid),
new FrameworkPropertyMetadata(GridLength.Auto, FrameworkPropertyMetadataOptions.AffectsMeasure, FixedColumnWidthChanged));
public static readonly DependencyProperty IsAutoIndexingProperty =
DependencyProperty.Register("IsAutoIndexing", typeof(bool), typeof(AutoGrid),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(AutoGrid),
new FrameworkPropertyMetadata(Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
public static readonly DependencyProperty RowCountProperty =
DependencyProperty.RegisterAttached("RowCount", typeof(int), typeof(AutoGrid),
new FrameworkPropertyMetadata(1, FrameworkPropertyMetadataOptions.AffectsMeasure, RowCountChanged));
public static readonly DependencyProperty RowHeightProperty =
DependencyProperty.RegisterAttached("RowHeight", typeof(GridLength), typeof(AutoGrid),
new FrameworkPropertyMetadata(GridLength.Auto, FrameworkPropertyMetadataOptions.AffectsMeasure, FixedRowHeightChanged));
public static readonly DependencyProperty RowsProperty =
DependencyProperty.RegisterAttached("Rows", typeof(string), typeof(AutoGrid),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsMeasure, RowsChanged));
#endregion
#region 依赖属性回调 - 行列生成与更新
/// <summary>
/// 列数量变化回调:自动生成对应数量的列
/// </summary>
public static void ColumnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((int)e.NewValue < 0) return;
var grid = d as AutoGrid;
if (grid == null) return;
// 复用现有列的宽度(默认Auto,或已设置的ColumnWidth)
var columnWidth = grid.ColumnDefinitions.Count > 0 ? grid.ColumnDefinitions[0].Width : GridLength.Auto;
// 清空现有列,重新生成
grid.ColumnDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
{
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = columnWidth });
}
}
/// <summary>
/// 列定义字符串变化回调:解析字符串生成列
/// </summary>
public static void ColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newColumns = (string)e.NewValue;
if (string.IsNullOrEmpty(newColumns)) return;
var grid = d as AutoGrid;
if (grid == null) return;
// 清空现有列,解析字符串生成新列
grid.ColumnDefinitions.Clear();
var gridLengths = Parse(newColumns);
foreach (var length in gridLengths)
{
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = length });
}
}
/// <summary>
/// 统一列宽度变化回调:批量更新所有列的宽度
/// </summary>
public static void FixedColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as AutoGrid;
if (grid == null) return;
// 无列时默认生成一列
if (grid.ColumnDefinitions.Count == 0)
{
grid.ColumnDefinitions.Add(new ColumnDefinition());
}
// 批量更新所有列的宽度
var newColumnWidth = (GridLength)e.NewValue;
for (int i = 0; i < grid.ColumnDefinitions.Count; i++)
{
grid.ColumnDefinitions[i].Width = newColumnWidth;
}
}
/// <summary>
/// 统一行高度变化回调:批量更新所有行的高度
/// </summary>
public static void FixedRowHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as AutoGrid;
if (grid == null) return;
// 无行时默认生成一行
if (grid.RowDefinitions.Count == 0)
{
grid.RowDefinitions.Add(new RowDefinition());
}
// 批量更新所有行的高度
var newRowHeight = (GridLength)e.NewValue;
for (int i = 0; i < grid.RowDefinitions.Count; i++)
{
grid.RowDefinitions[i].Height = newRowHeight;
}
}
/// <summary>
/// 行数量变化回调:自动生成对应数量的行
/// </summary>
public static void RowCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((int)e.NewValue < 0) return;
var grid = d as AutoGrid;
if (grid == null) return;
// 复用现有行的高度(默认Auto,或已设置的RowHeight)
var rowHeight = grid.RowDefinitions.Count > 0 ? grid.RowDefinitions[0].Height : GridLength.Auto;
// 清空现有行,重新生成
grid.RowDefinitions.Clear();
for (int i = 0; i < (int)e.NewValue; i++)
{
grid.RowDefinitions.Add(new RowDefinition() { Height = rowHeight });
}
}
/// <summary>
/// 行定义字符串变化回调:解析字符串生成行
/// </summary>
public static void RowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var newRows = (string)e.NewValue;
if (string.IsNullOrEmpty(newRows)) return;
var grid = d as AutoGrid;
if (grid == null) return;
// 清空现有行,解析字符串生成新行
grid.RowDefinitions.Clear();
var gridLengths = Parse(newRows);
foreach (var length in gridLengths)
{
grid.RowDefinitions.Add(new RowDefinition() { Height = length });
}
}
#endregion
#region 私有回调 - 子控件样式更新
/// <summary>
/// 子控件水平对齐方式变化回调:批量更新所有子控件
/// </summary>
private static void OnChildHorizontalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as AutoGrid;
if (grid == null) return;
foreach (UIElement child in grid.Children)
{
if (grid.ChildHorizontalAlignment.HasValue)
{
child.SetValue(FrameworkElement.HorizontalAlignmentProperty, grid.ChildHorizontalAlignment);
}
else
{
child.SetValue(FrameworkElement.HorizontalAlignmentProperty, DependencyProperty.UnsetValue);
}
}
}
/// <summary>
/// 子控件边距变化回调:批量更新所有子控件
/// </summary>
private static void OnChildMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as AutoGrid;
if (grid == null) return;
foreach (UIElement child in grid.Children)
{
if (grid.ChildMargin.HasValue)
{
child.SetValue(FrameworkElement.MarginProperty, grid.ChildMargin);
}
else
{
child.SetValue(FrameworkElement.MarginProperty, DependencyProperty.UnsetValue);
}
}
}
/// <summary>
/// 子控件垂直对齐方式变化回调:批量更新所有子控件
/// </summary>
private static void OnChildVerticalAlignmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as AutoGrid;
if (grid == null) return;
foreach (UIElement child in grid.Children)
{
if (grid.ChildVerticalAlignment.HasValue)
{
child.SetValue(FrameworkElement.VerticalAlignmentProperty, grid.ChildVerticalAlignment);
}
else
{
child.SetValue(FrameworkElement.VerticalAlignmentProperty, DependencyProperty.UnsetValue);
}
}
}
#endregion
#region 核心工具方法
/// <summary>
/// 解析逗号分隔的字符串,转换为GridLength数组
/// 支持三种格式:*(比例)、像素(数字)、Auto(默认)
/// </summary>
public static GridLength[] Parse(string text)
{
if (string.IsNullOrEmpty(text)) return Array.Empty<GridLength>();
var tokens = text.Split(',');
var gridLengths = new GridLength[tokens.Length];
for (var i = 0; i < tokens.Length; i++)
{
var token = tokens[i].Trim(); // 去除空格,提升兼容性
if (string.IsNullOrEmpty(token))
{
gridLengths[i] = GridLength.Auto;
continue;
}
// 处理比例格式(*),如"2*"表示占2份比例
if (token.Contains('*'))
{
var valueStr = token.Replace("*", "");
double ratio = string.IsNullOrEmpty(valueStr) ? 1.0 : (double.TryParse(valueStr, out var r) ? r : 1.0);
gridLengths[i] = new GridLength(ratio, GridUnitType.Star);
continue;
}
// 处理像素格式(纯数字),如"100"表示100像素
if (double.TryParse(token, out var pixelValue))
{
gridLengths[i] = new GridLength(pixelValue);
continue;
}
// 默认Auto格式
gridLengths[i] = GridLength.Auto;
}
return gridLengths;
}
/// <summary>
/// 限制数值在最大范围内,防止索引越界
/// </summary>
private int Clamp(int value, int max)
{
return Math.Min(value, max);
}
/// <summary>
/// 应用子控件的统一样式(边距、对齐方式)
/// 仅当子控件对应属性为默认值时才赋值,支持单个子控件覆盖
/// </summary>
private void ApplyChildLayout(UIElement child)
{
if (child == null) return;
// 统一边距
if (ChildMargin.HasValue)
{
child.SetIfDefault(FrameworkElement.MarginProperty, ChildMargin.Value);
}
// 统一水平对齐
if (ChildHorizontalAlignment.HasValue)
{
child.SetIfDefault(FrameworkElement.HorizontalAlignmentProperty, ChildHorizontalAlignment.Value);
}
// 统一垂直对齐
if (ChildVerticalAlignment.HasValue)
{
child.SetIfDefault(FrameworkElement.VerticalAlignmentProperty, ChildVerticalAlignment.Value);
}
}
#endregion
#region 核心布局逻辑 - 子控件自动排列
/// <summary>
/// 执行自动布局:计算子控件的Grid.Row和Grid.Column
/// </summary>
private void PerformLayout()
{
// 禁用自动索引或无行列时,直接返回
if (!IsAutoIndexing || RowDefinitions.Count == 0 || ColumnDefinitions.Count == 0)
{
return;
}
var isRowFirst = Orientation == Orientation.Horizontal; // 先行后列(横向)
var rowCount = RowDefinitions.Count;
var colCount = ColumnDefinitions.Count;
var position = 0; // 记录当前布局位置
var skipGrid = new bool[rowCount, colCount]; // 标记被RowSpan/ColumnSpan占用的格子,避免重叠
foreach (UIElement child in Children)
{
// 折叠状态的子控件不参与自动布局
if (child.Visibility == Visibility.Collapsed)
{
ApplyChildLayout(child);
continue;
}
if (IsAutoIndexing)
{
int targetRow = 0;
int targetCol = 0;
if (isRowFirst)
{
// 先行后列:行=position/列数,列=position%列数
targetRow = Clamp(position / colCount, rowCount - 1);
targetCol = Clamp(position % colCount, colCount - 1);
// 跳过已被占用的格子
if (skipGrid[targetRow, targetCol])
{
position++;
targetRow = Clamp(position / colCount, rowCount - 1);
targetCol = Clamp(position % colCount, colCount - 1);
}
// 设置子控件的行列索引
Grid.SetRow(child, targetRow);
Grid.SetColumn(child, targetCol);
// 更新下一个位置(跳过列跨度的格子)
position += Grid.GetColumnSpan(child);
// 标记RowSpan占用的格子(纵向占用)
var rowSpanOffset = Grid.GetRowSpan(child) - 1;
while (rowSpanOffset > 0 && targetRow + rowSpanOffset < rowCount)
{
skipGrid[targetRow + rowSpanOffset, targetCol] = true;
rowSpanOffset--;
}
}
else
{
// 先列后行:行=position%行数,列=position/行数
targetRow = Clamp(position % rowCount, rowCount - 1);
targetCol = Clamp(position / rowCount, colCount - 1);
// 跳过已被占用的格子
if (skipGrid[targetRow, targetCol])
{
position++;
targetRow = Clamp(position % rowCount, rowCount - 1);
targetCol = Clamp(position / rowCount, colCount - 1);
}
// 设置子控件的行列索引
Grid.SetRow(child, targetRow);
Grid.SetColumn(child, targetCol);
// 更新下一个位置(跳跨行跨度的格子)
position += Grid.GetRowSpan(child);
// 标记ColumnSpan占用的格子(横向占用)
var colSpanOffset = Grid.GetColumnSpan(child) - 1;
while (colSpanOffset > 0 && targetCol + colSpanOffset < colCount)
{
skipGrid[targetRow, targetCol + colSpanOffset] = true;
colSpanOffset--;
}
}
}
// 应用统一样式
ApplyChildLayout(child);
}
}
#endregion
#region 重写控件生命周期 - 确保布局生效
/// <summary>
/// 重写测量方法:在测量子控件尺寸前执行自动布局
/// </summary>
/// <param name="constraint">布局上限尺寸</param>
/// <returns>所需布局尺寸</returns>
protected override Size MeasureOverride(Size constraint)
{
PerformLayout(); // 测量前先完成自动布局,确保子控件位置正确
return base.MeasureOverride(constraint);
}
#endregion
}
}
三、核心原理深度解析
AutoGrid的实现基于 WPF 控件的生命周期和依赖属性机制,核心逻辑可分为 3 个部分:
3.1 依赖属性:控件配置的核心载体
WPF 自定义控件优先使用依赖属性(DependencyProperty) 而非普通 CLR 属性,原因是依赖属性支持数据绑定、样式应用、属性变更回调、元数据配置等功能,这是实现 AutoGrid 自动布局的基础。
- 关键元数据配置:
FrameworkPropertyMetadataOptions.AffectsMeasure,标记该属性变化会触发控件的测量流程(MeasureOverride),进而重新执行PerformLayout,实现布局实时更新; - 两种注册方式:
Register(控件自身属性,如ChildMargin)和RegisterAttached(附加属性,如ColumnCount),附加属性允许其他元素访问,提升控件灵活性; - 属性变更回调:如
ColumnCountChanged、OnChildMarginChanged,当属性值变化时,自动执行行列生成或子控件样式更新,无需手动调用。
3.2 行列生成:两种方式灵活配置
AutoGrid提供两种行列配置方式,满足不同场景需求:
- 简单配置(数量 + 统一尺寸):通过
ColumnCount/RowCount设置行列数量,ColumnWidth/RowHeight设置统一尺寸,适合行列尺寸一致的场景(如网格列表); - 自定义配置(字符串解析):通过
Columns/Rows设置逗号分隔的字符串,支持*(比例)、像素、Auto三种格式,适合行列尺寸不一致的场景(如表单布局); - 核心解析方法
Parse:将字符串转换为GridLength数组,是自定义行列配置的关键,处理了空格兼容、默认值容错,提升了使用便捷性。
3.3 自动布局:子控件位置计算与冲突避免
PerformLayout是 AutoGrid 的核心方法,负责计算每个子控件的Grid.Row和Grid.Column,核心逻辑如下:
- 布局方向判断:
Horizontal(先行后列,类似 Excel 横向填充)、Vertical(先列后行,类似 Excel 纵向填充); - 位置计算:通过
position变量记录当前布局位置,利用除法 / 取模 运算计算行列索引(position/列数= 行,position%列数= 列); - 越界防护:
Clamp方法限制索引在行列数量范围内,避免IndexOutOfRangeException; - 跨度处理:
skipGrid二维数组标记RowSpan/ColumnSpan占用的格子,确保后续子控件跳过已占用位置,避免重叠; - 样式应用:
ApplyChildLayout调用SetIfDefault扩展方法,仅当子控件属性为默认值时才赋值,兼顾统一样式和单个控件的灵活性。
3.4 控件生命周期:确保布局实时生效
AutoGrid重写了Grid的MeasureOverride方法,在控件测量子控件尺寸之前 执行PerformLayout,确保子控件的位置和样式在测量、排列阶段已配置完成,最终呈现正确的布局效果。
同时,依赖属性的AffectsMeasure元数据配置,确保当布局相关属性(如Orientation、ColumnCount)变化时,自动触发MeasureOverride,实现布局的实时更新。
四、使用示例(实战演示)
4.1 环境准备
使用AutoGrid前,需在 XAML 中引用对应的命名空间(假设程序集名称为WpfAutoGrid):
XML
xmlns:ag="clr-namespace:WpfAutoGrid;assembly=WpfAutoGrid"
4.2 示例 1:简单网格布局(统一行列尺寸)
需求:创建 3 列 2 行的网格,子控件统一边距,横向优先填充,统一居中对齐。
XML
<ag:AutoGrid ColumnCount="3"
RowCount="2"
ColumnWidth="100"
RowHeight="60"
ChildMargin="5"
ChildHorizontalAlignment="Center"
ChildVerticalAlignment="Center"
Background="#F5F5F5">
<Button Content="按钮1" />
<Button Content="按钮2" />
<Button Content="按钮3" />
<Button Content="按钮4" />
<Button Content="按钮5" Background="LightBlue" HorizontalAlignment="Left" /> <!-- 覆盖水平对齐 -->
</ag:AutoGrid>
效果说明:
- 自动生成 3 列(每列 100 像素)、2 行(每行 60 像素);
- 5 个按钮自动横向填充,统一边距 5,居中对齐;
- 按钮 5 显式设置
HorizontalAlignment="Left",覆盖统一样式,呈现左对齐效果。
4.3 示例 2:自定义行列尺寸(表单布局)
需求:创建表单布局,列定义为「100 像素(标签)、*(输入框)」,行定义为「Auto、Auto、60、Auto」,纵向优先填充。
XML
<ag:AutoGrid Columns="100,*"
Rows="Auto,Auto,60,Auto"
Orientation="Vertical"
ChildMargin="5,3"
Background="#F5F5F5">
<TextBlock Text="用户名:" />
<TextBox Text="请输入用户名" />
<TextBlock Text="密码:" />
<PasswordBox Password="123456" />
<Button Content="登录" Grid.ColumnSpan="2" /> <!-- 跨2列 -->
<TextBlock Text="备注:" />
<TextBox Text="请输入备注" AcceptsReturn="True" Grid.RowSpan="2" /> <!-- 跨2行 -->
</ag:AutoGrid>
效果说明:
- 列解析为「100 像素、剩余空间填满」,行解析为「Auto、Auto、60 像素、Auto」;
- 子控件纵向优先填充,表单布局整洁有序;
- 登录按钮跨 2 列,备注输入框跨 2 行,
skipGrid自动标记占用格子,无重叠。
五、总结与扩展建议
5.1 核心总结
AutoGrid基于原生Grid扩展,兼容Grid的所有功能,同时提供自动布局能力,简化网格布局开发;- 核心优势:自动生成行列 、自动排列子控件 、统一样式配置,兼顾灵活性和易用性;
- 核心机制:依赖属性变更回调、控件生命周期重写、行列索引计算与冲突避免;
- 关键扩展:
SetIfDefault方法实现「统一样式 + 单个控件覆盖」的需求,提升控件的灵活性。
5.2 扩展建议
- 支持行列间距配置:添加
ColumnSpacing和RowSpacing属性,实现行列之间的间距控制; - 支持自适应换行:当子控件数量超过当前行列容量时,自动新增行 / 列,无需手动设置
ColumnCount/RowCount; - 支持排序功能:添加
SortDirection属性,实现子控件的升序 / 降序排列; - 支持数据绑定:结合
ItemsControl,实现数据集合的自动绑定与布局,适配列表场景。
AutoGrid大幅简化了 WPF 网格布局的开发成本,尤其适合表单、网格列表等场景,希望本文能帮助你理解其核心原理,并在实际项目中灵活运用。