C# WinForms DataGridView 深度应用与核心技巧详解
自定义控件的初始化与数据绑定
在使用扩展的 DataGridView 控件(如 DataGridViewEx)之前,首先需配置数据库连接并正确设置数据源。以下示例展示了如何在应用程序启动时初始化服务,以及在窗口加载时绑定数据。
// 在 Program.cs 中配置数据库连接
SqlSugarServices.SetConnectionString("server=192.168.1.100,1433;database=system_db;uid=dev_user;pwd=secure_password");
// 在窗体的 Load 事件中进行初始化
private void InitializeCustomGrid()
{
// 配置扩展属性:关联用户、窗体类型及控件标识
customDataGrid.SetColRelate("admin", this.GetType().FullName, "mainGrid", "1");
// 准备数据绑定组件
BindingSource bindingSrc = new BindingSource();
DataTable sourceData = new DataRepository().GetTableData();
// 设置数据源
bindingSrc.DataSource = sourceData;
customDataGrid.DataSource = bindingSrc;
customDataGrid.DataMember = sourceData.TableName;
// 将 BindingSource 引用赋给自定义控件的扩展属性
customDataGrid.BoundSource = bindingSrc;
}
界面样式与列宽调整
为了提升用户体验,通常需要对表头对齐、列宽自适应以及单元格颜色进行定制。
// 设置表头居中对齐
mainGrid.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
// 设置特定列(如第二列)居中并禁用排序
mainGrid.Columns[1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
mainGrid.Columns[1].SortMode = DataGridViewColumnSortMode.NotSortable;
// 自动调整列宽以适应单元格内容
mainGrid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
// 也可以单独设置某一列的自适应模式
mainGrid.Columns[0].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
自动调整模式说明:
AllCells:根据列中所有单元格(包括表头)的内容调整宽度。
DisplayedCells:仅根据当前可见的单元格内容调整。
Fill:列宽将填充控件的可用区域,通常配合 FillWeight 属性使用。
// 设置 Fill 模式下的相对权重
mainGrid.Columns[0].FillWeight = 20; // 占用 20% 宽度
mainGrid.Columns[1].FillWeight = 80; // 占用 80% 宽度
单元格交互与数据获取
处理鼠标点击事件并获取当前单元格数据是常见的需求。同时,理解 Value 和 EditedFormattedValue 的区别至关重要。
数据属性区别:
EditedFormattedValue:单元格当前正在编辑且尚未提交到底层数据源的值(例如用户正在输入但未按回车)。
Value:单元格已提交的实际数据值。
private void OnCellClick(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex < 0 || e.ColumnIndex < 0) return;
// 方法一:直接使用坐标获取
int rowIndex = e.RowIndex;
int colIndex = e.ColumnIndex;
// 方法二:通过当前选中状态获取
int currentRow = mainGrid.CurrentCell.RowIndex;
var cellContent = mainGrid.CurrentCell.Value;
MessageBox.Show($"选中: 第 {rowIndex + 1} 行, 内容: {cellContent}");
}
行与列的高级控制
包括设置行号、禁止编辑特定列以及遍历选中行。
// 1. 实现行号显示(处理 RowStateChanged 事件)
private void OnRowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
{
e.Row.HeaderCell.Value = (e.Row.Index + 1).ToString();
}
// 2. 设置特定列只读并修改样式
mainGrid.EnableHeadersVisualStyles = false; // 允许自定义表头样式
mainGrid.Columns[3].ReadOnly = true; // 禁止编辑第4列
mainGrid.Columns[3].HeaderCell.Style.BackColor = Color.LightBlue;
mainGrid.Columns[3].DefaultCellStyle.BackColor = Color.LightGray;
// 3. 遍历并处理选中行
foreach (DataGridViewRow row in mainGrid.SelectedRows)
{
var id = row.Cells["UserID"].Value.ToString();
// 执行业务逻辑...
}
数据验证与输入控制
在单元格编辑完成后验证数据类型,例如限制只能输入数字。
private void OnCellEndEdit(object sender, DataGridViewCellEventArgs e)
{
string input = mainGrid[e.ColumnIndex, e.RowIndex].Value?.ToString();
if (string.IsNullOrWhiteSpace(input))
{
return; // 允许空值,不做处理
}
if (decimal.TryParse(input, out decimal number))
{
// 如果是数字,自动跳转到下一行对应列
if (e.RowIndex + 1 < mainGrid.Rows.Count)
{
mainGrid.CurrentCell = mainGrid[e.ColumnIndex, e.RowIndex + 1];
}
}
else
{
MessageBox.Show("输入无效:请输入数字。");
mainGrid.BeginEdit(true); // 重新进入编辑模式
}
}
实现底部合计行(Footer)
通过自定义绘制来实现固定在底部的合计行,这种方式不随滚动条移动,且不影响数据源。
private void OnCellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
// 确保启用了添加行选项,且仅处理新行(底部的空白行)的绘制
if (e.RowIndex == mainGrid.NewRowIndex && e.ColumnIndex >= 0)
{
int total = 0;
// 计算当前列的总和
for (int i = 0; i < mainGrid.RowCount - 1; i++) // 排除新行本身
{
object val = mainGrid.Rows[i].Cells[e.ColumnIndex].Value;
if (val != null && int.TryParse(val.ToString(), out int num))
{
total += num;
}
}
// 绘制背景
e.PaintBackground(e.CellBounds, true);
// 自定义绘制合计文本
string text = total.ToString();
TextRenderer.DrawText(e.Graphics, text, mainGrid.Font,
new Point(e.CellBounds.X + 5, e.CellBounds.Y + 5),
Color.Red, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
e.Handled = true; // 阻止系统默认绘制
}
}
DataBindingComplete 事件触发优化
在绑定数据源时,如果先设置 DataMember 后设置 DataSource,可能会导致 DataBindingComplete 事件触发多次。建议按以下顺序赋值:
// 正确的绑定顺序
mainGrid.DataSource = null; // 先清空
mainGrid.DataMember = "TargetTable"; // 再指定成员
mainGrid.DataSource = dataSet; // 最后绑定源
这种顺序可以避免因属性自动更新导致的重复事件触发,从而提高性能并减少不必要的逻辑执行。