1. 项目缘起:为什么DataGridView是WinForm开发的“定海神针”?
如果你正在用C#开发Windows桌面应用,尤其是那些需要展示、编辑、管理表格数据的应用,比如库存管理系统、客户信息表、订单列表或者日志查看器,那么你几乎绕不开一个控件:DataGridView。它不是最炫酷的,但绝对是使用频率最高、功能最强大的数据展示控件之一。很多新手在初次接触时,可能会被它复杂的属性、事件和方法吓到,觉得它“又大又笨重”。但当你真正掌握其核心用法后,你会发现它就像一个瑞士军刀,能解决你80%以上的表格数据交互需求。今天,我们就从一个最基础、最实用的角度切入,通过一个完整的“员工信息管理”实例,手把手带你搞懂DataGridView的核心三板斧:数据绑定、样式定制和事件处理。我们不会一开始就钻进那些高级的“虚拟模式”或“自定义单元格绘制”,而是先把地基打牢,让你能快速做出一个功能完整、界面美观的数据表格应用。相信我,理解了这些基础,再去探索高级特性,你会事半功倍。
2. 环境准备与项目搭建:从零创建一个WinForm应用
在开始编码之前,我们得先把“厨房”收拾好。这里我们使用最经典的开发环境:Visual Studio 2022 和 .NET Framework 4.8(当然,.NET 6/8 的 WinForms 项目也完全适用,核心API几乎一致)。
2.1 创建项目与界面布局
首先,打开Visual Studio,创建一个新的“Windows窗体应用(.NET Framework)”项目,命名为EmployeeManager。
- 设计主窗体:打开默认的
Form1,将它的Text属性修改为“员工信息管理”。 - 拖放控件:从工具箱的“所有Windows窗体”或“公共控件”分组中,找到
DataGridView控件,将其拖拽到窗体上。调整其Dock属性为Fill,让它铺满整个窗体客户区。 - 添加操作按钮:在工具箱中找到
Button控件,在窗体的顶部或底部(可以通过一个Panel容器来布局)添加四个按钮,分别命名为:btnAdd,btnEdit,btnDelete,btnRefresh。它们的Text属性分别设置为“新增(&A)”、“编辑(&E)”、“删除(&D)”、“刷新(&R)”。这里的&符号用于定义快捷键(Alt+A等)。 - 最终布局预览:一个简单的界面就出来了:顶部是一排操作按钮,下方是一个占据主要区域的
DataGridView,用于展示员工列表。
提示:在拖放
DataGridView后,右上角会出现一个智能标记(小箭头),点击它可以快速进行一些常用操作,如选择数据源、启用添加/编辑/删除等。但我们为了彻底理解原理,先不通过这个向导,而是全部通过代码来实现。
2.2 定义数据模型:员工类 (Employee)
任何数据绑定的核心都是数据模型。我们先创建一个简单的类来表示员工信息。在项目中右键,选择“添加” -> “类”,命名为Employee.cs。
namespace EmployeeManager { public class Employee { public int Id { get; set; } // 员工ID public string Name { get; set; } // 姓名 public string Department { get; set; } // 部门 public string Position { get; set; } // 职位 public DateTime HireDate { get; set; } // 入职日期 public decimal Salary { get; set; } // 薪资 // 为了方便演示,提供一个生成示例数据的方法 public static List<Employee> GetSampleData() { return new List<Employee> { new Employee { Id = 1, Name = "张三", Department = "技术部", Position = "高级工程师", HireDate = new DateTime(2020, 5, 10), Salary = 25000 }, new Employee { Id = 2, Name = "李四", Department = "市场部", Position = "经理", HireDate = new DateTime(2019, 8, 22), Salary = 18000 }, new Employee { Id = 3, Name = "王五", Department = "财务部", Position = "会计", HireDate = new DateTime(2021, 3, 15), Salary = 8000 }, new Employee { Id = 4, Name = "赵六", Department = "技术部", Position = "实习生", HireDate = new DateTime(2023, 7, 1), Salary = 4000 }, new Employee { Id = 5, Name = "钱七", Department = "人事部", Position = "专员", HireDate = new DateTime(2022, 11, 30), Salary = 7500 } }; } } }这个Employee类就是我们的数据实体。GetSampleData方法提供了一个内存中的模拟数据源,在实际项目中,这里应该替换为从数据库(如SQL Server, MySQL)或Web API获取数据的逻辑。
3. 核心绑定:将数据“塞进”DataGridView的三种姿势
数据有了,界面有了,现在如何把它们连接起来?这就是数据绑定。DataGridView支持多种数据源,我们主要讲最常用的两种:List<T>和DataTable。
3.1 方式一:绑定List (最推荐的方式)
这是面向对象编程中最自然的方式。我们直接在窗体的加载事件中,将Employee列表绑定到DataGridView。
首先,在Form1类中声明一个成员变量来持有数据:
private List<Employee> employeeList;然后,在Form1_Load事件处理方法中编写代码:
private void Form1_Load(object sender, EventArgs e) { // 1. 获取数据 employeeList = Employee.GetSampleData(); // 2. 设置数据源 dataGridView1.DataSource = employeeList; // 3. (可选)隐藏自动生成的ID列,通常ID不需要展示给用户 dataGridView1.Columns["Id"].Visible = false; }就这么简单!运行程序,你会看到表格自动生成了与Employee类属性对应的列,并填满了数据。DataGridView通过反射读取了类的公共属性,并自动创建了列。这种方式的好处是强类型,编译时就能发现属性名错误,并且便于后续对列表进行增删改查操作。
3.2 方式二:绑定DataTable(传统ADO.NET风格)
如果你处理的是从数据库直接查询出来的DataTable,或者需要更动态的列结构,也可以直接绑定DataTable。
private void BindDataTable() { DataTable dt = new DataTable("Employees"); // 定义列 dt.Columns.Add("Id", typeof(int)); dt.Columns.Add("Name", typeof(string)); dt.Columns.Add("Department", typeof(string)); // ... 添加其他列 // 填充行数据 dt.Rows.Add(1, "张三", "技术部"); dt.Rows.Add(2, "李四", "市场部"); // ... 添加其他行 dataGridView1.DataSource = dt; }这种方式更灵活,列可以动态添加删除,但失去了强类型的好处,容易因列名拼写错误导致运行时异常。
3.3 手动创建列(精细化控制)
自动生成的列虽然方便,但往往不符合我们的UI要求。比如,我们想把“HireDate”显示为“入职日期”,把“Salary”格式化为货币并右对齐。这时就需要手动创建列。
我们可以在绑定数据源之前,先配置好DataGridView的列。将Form1_Load中的代码修改如下:
private void Form1_Load(object sender, EventArgs e) { // 先配置列,再绑定数据源 ConfigureDataGridViewColumns(); employeeList = Employee.GetSampleData(); dataGridView1.DataSource = employeeList; } private void ConfigureDataGridViewColumns() { // 清除可能存在的自动生成列 dataGridView1.AutoGenerateColumns = false; // 手动添加列 // 文本列 - 姓名 DataGridViewTextBoxColumn colName = new DataGridViewTextBoxColumn(); colName.Name = "colName"; // 列在代码中的标识 colName.HeaderText = "姓名"; // 列标题显示的文字 colName.DataPropertyName = "Name"; // 绑定的数据源属性名 colName.Width = 100; dataGridView1.Columns.Add(colName); // 文本列 - 部门 DataGridViewTextBoxColumn colDept = new DataGridViewTextBoxColumn(); colDept.Name = "colDept"; colDept.HeaderText = "部门"; colDept.DataPropertyName = "Department"; colDept.Width = 120; dataGridView1.Columns.Add(colDept); // 文本列 - 职位 DataGridViewTextBoxColumn colPos = new DataGridViewTextBoxColumn(); colPos.Name = "colPos"; colPos.HeaderText = "职位"; colPos.DataPropertyName = "Position"; colPos.Width = 120; dataGridView1.Columns.Add(colPos); // 日期列 - 入职日期 DataGridViewTextBoxColumn colHireDate = new DataGridViewTextBoxColumn(); // 仍用TextBox列,但设置格式 colHireDate.Name = "colHireDate"; colHireDate.HeaderText = "入职日期"; colHireDate.DataPropertyName = "HireDate"; colHireDate.Width = 100; colHireDate.DefaultCellStyle.Format = "yyyy-MM-dd"; // 设置日期格式 colHireDate.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter; dataGridView1.Columns.Add(colHireDate); // 文本列 - 薪资(货币格式) DataGridViewTextBoxColumn colSalary = new DataGridViewTextBoxColumn(); colSalary.Name = "colSalary"; colSalary.HeaderText = "月薪"; colSalary.DataPropertyName = "Salary"; colSalary.Width = 100; colSalary.DefaultCellStyle.Format = "C2"; // “C2”表示货币格式,保留两位小数 colSalary.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; dataGridView1.Columns.Add(colSalary); // 按钮列 - 操作(如查看详情) DataGridViewButtonColumn colAction = new DataGridViewButtonColumn(); colAction.Name = "colAction"; colAction.HeaderText = "操作"; colAction.Text = "详情"; colAction.UseColumnTextForButtonValue = true; // 按钮显示统一的文本 colAction.Width = 80; dataGridView1.Columns.Add(colAction); }关键点在于AutoGenerateColumns = false和DataPropertyName属性。前者告诉控件不要自动创建列,后者指定了该列绑定到数据源的哪个属性。通过手动创建列,我们获得了对列标题、宽度、数据格式、对齐方式等的完全控制权。我们还添加了一个按钮列,这为后续处理行内操作事件打下了基础。
4. 样式美化与用户体验提升
一个光秃秃的表格是缺乏美感和可用性的。DataGridView提供了丰富的样式属性,可以轻松美化。
4.1 设置交替行背景色(斑马线效果)
让奇偶行显示不同的背景色,可以极大提高长表格的可读性。
private void ApplyZebraStriping() { dataGridView1.AlternatingRowsDefaultCellStyle.BackColor = Color.LightCyan; // 你也可以设置非交替行的默认背景色 // dataGridView1.RowsDefaultCellStyle.BackColor = Color.White; }4.2 设置列头与网格线样式
private void ApplyGridStyles() { // 1. 列头样式 dataGridView1.ColumnHeadersDefaultCellStyle.BackColor = Color.Navy; dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor = Color.White; dataGridView1.ColumnHeadersDefaultCellStyle.Font = new Font("微软雅黑", 10, FontStyle.Bold); dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.EnableResizing; dataGridView1.ColumnHeadersHeight = 40; // 设置列头高度 // 2. 网格线颜色 dataGridView1.GridColor = Color.LightGray; // 3. 边框样式 dataGridView1.BorderStyle = BorderStyle.Fixed3D; // 4. 启用行头(最左侧的灰色区域),但可以隐藏 dataGridView1.RowHeadersVisible = false; // 5. 允许用户调整列宽和行高 dataGridView1.AllowUserToResizeColumns = true; dataGridView1.AllowUserToResizeRows = false; // 通常行高不需要调整 // 6. 选择模式:整行选择 dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect; dataGridView1.MultiSelect = false; // 禁止多选,简化操作 }4.3 条件格式:高亮特定数据
我们可以根据单元格的值来动态设置其样式。例如,高亮显示薪资高于20000的员工。
private void ApplyConditionalFormatting() { // 使用CellFormatting事件动态设置样式 dataGridView1.CellFormatting += (sender, e) => { if (e.RowIndex >= 0 && e.ColumnIndex >= 0) // 确保是数据行,不是列头或行头 { DataGridView dgv = sender as DataGridView; if (dgv.Columns[e.ColumnIndex].DataPropertyName == "Salary") // 只处理薪资列 { if (dgv.Rows[e.RowIndex].DataBoundItem is Employee emp) { if (emp.Salary > 20000m) { e.CellStyle.BackColor = Color.LightGoldenrodYellow; e.CellStyle.Font = new Font(e.CellStyle.Font, FontStyle.Bold); e.CellStyle.ForeColor = Color.DarkRed; } } } } }; }CellFormatting事件在单元格即将被绘制时触发,是进行条件格式化的绝佳位置。注意,这里我们通过DataBoundItem获取到了绑定到该行的原始Employee对象,这是一种安全且强类型的访问方式。
5. 实现增删改查:让表格“活”起来
静态的表格没有灵魂。现在,我们为之前添加的四个按钮实现功能,并与DataGridView联动。
5.1 刷新数据
这是最简单的。刷新按钮通常用于重新从数据源(数据库)加载数据。
private void btnRefresh_Click(object sender, EventArgs e) { // 模拟从数据库重新加载 employeeList = Employee.GetSampleData(); // 实际项目中,这里应是数据库查询 dataGridView1.DataSource = null; // 先清空现有绑定 dataGridView1.DataSource = employeeList; // 重新绑定 // 如果手动配置了列,记得重新应用列配置或保持AutoGenerateColumns=false }5.2 新增与编辑数据
新增和编辑通常通过一个独立的“编辑窗体”来完成。我们先创建一个新的窗体FormEmployeeEdit。
- 创建编辑窗体:添加新窗体,设计界面,包含对应
Employee属性的TextBox、ComboBox等控件,以及“确定”、“取消”按钮。 - 在Form1中实现新增逻辑:
private void btnAdd_Click(object sender, EventArgs e) { using (FormEmployeeEdit editForm = new FormEmployeeEdit()) { editForm.Text = "新增员工"; if (editForm.ShowDialog() == DialogResult.OK) { Employee newEmp = editForm.GetEmployee(); // 假设编辑窗体有一个方法返回编辑后的Employee对象 newEmp.Id = employeeList.Max(e => e.Id) + 1; // 生成新ID(模拟,实际应由数据库生成) employeeList.Add(newEmp); // 刷新DataGridView绑定 // 方法1:重新绑定整个列表(简单但效率略低) dataGridView1.DataSource = null; dataGridView1.DataSource = employeeList; // 方法2:如果DataGridView绑定的是BindingSource,可以调用ResetBindings // bindingSource.ResetBindings(false); } } }- 在Form1中实现编辑逻辑:
编辑需要获取当前选中的行。
private void btnEdit_Click(object sender, EventArgs e) { if (dataGridView1.CurrentRow == null || dataGridView1.CurrentRow.Index < 0) { MessageBox.Show("请先选择要编辑的员工行。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } // 获取选中行绑定的数据对象 Employee selectedEmp = dataGridView1.CurrentRow.DataBoundItem as Employee; if (selectedEmp == null) return; using (FormEmployeeEdit editForm = new FormEmployeeEdit(selectedEmp)) // 假设构造函数接收一个Employee对象用于初始化 { editForm.Text = "编辑员工信息"; if (editForm.ShowDialog() == DialogResult.OK) { Employee updatedEmp = editForm.GetEmployee(); // 更新列表中的对象(因为是引用类型,selectedEmp已被编辑窗体修改,这里通常只需刷新显示) // 如果编辑窗体返回的是新对象,则需要替换原对象 // int index = employeeList.IndexOf(selectedEmp); // employeeList[index] = updatedEmp; // 强制刷新当前行的显示 dataGridView1.InvalidateRow(dataGridView1.CurrentRow.Index); } } }这里的关键是dataGridView1.CurrentRow.DataBoundItem,它直接拿到了绑定到该行的原始数据对象。修改这个对象,DataGridView中显示的数据并不会自动更新,因为绑定的是对象的引用,对象的属性值变了,但引用没变。我们需要调用InvalidateRow来触发重绘,或者通过BindingSource的机制通知界面更新。
5.3 删除数据
删除操作需要谨慎,通常需要确认。
private void btnDelete_Click(object sender, EventArgs e) { if (dataGridView1.CurrentRow == null) { MessageBox.Show("请先选择要删除的员工行。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } Employee empToDelete = dataGridView1.CurrentRow.DataBoundItem as Employee; if (empToDelete == null) return; DialogResult result = MessageBox.Show($"确定要删除员工【{empToDelete.Name}】吗?", "确认删除", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (result == DialogResult.Yes) { employeeList.Remove(empToDelete); // 由于我们绑定的是List,直接移除对象后,需要重置绑定源来更新UI // 简单做法:重新绑定 dataGridView1.DataSource = null; dataGridView1.DataSource = employeeList; // 更优做法:使用BindingSource,调用RemoveCurrent() } }直接操作List后,DataGridView不会自动感知变化。重置DataSource是一种方法,但在数据量大时效率不高。最佳实践是使用BindingSource组件。
5.4 使用BindingSource进行优雅的数据绑定
BindingSource是一个中间层,它封装了数据源,并提供了更强大的变更通知、排序、过滤和导航功能。
- 从工具箱拖一个
BindingSource组件到窗体上,默认名称为bindingSource1。 - 修改绑定代码:
private BindingSource bindingSource1 = new BindingSource(); // 如果在设计器拖放,会自动生成 private void Form1_Load(object sender, EventArgs e) { ConfigureDataGridViewColumns(); employeeList = Employee.GetSampleData(); // 将List绑定到BindingSource bindingSource1.DataSource = employeeList; // 将DataGridView绑定到BindingSource dataGridView1.DataSource = bindingSource1; }- 修改增删改查代码:
- 新增:
bindingSource1.Add(newEmp); - 编辑:修改对象属性后,调用
bindingSource1.ResetCurrentItem(); - 删除:
bindingSource1.RemoveCurrent();
- 新增:
使用BindingSource后,所有对数据的增删改操作都会自动同步反映到DataGridView上,无需手动重置DataSource,代码更简洁,性能也更好。
6. 处理用户交互:事件驱动编程
DataGridView提供了大量事件,让我们能够响应用户的点击、编辑、选择等操作。
6.1 处理单元格点击与按钮列点击
我们之前添加了一个“详情”按钮列。如何响应它的点击事件?
private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) { // CellContentClick事件在单元格内的内容(如按钮)被点击时触发 DataGridView dgv = sender as DataGridView; // 确保点击的是有效的单元格,并且是我们定义的按钮列 if (e.RowIndex >= 0 && e.ColumnIndex >= 0 && dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn) { // 获取对应行的数据 Employee emp = dgv.Rows[e.RowIndex].DataBoundItem as Employee; if (emp != null) { MessageBox.Show($"员工详情:\n姓名:{emp.Name}\n部门:{emp.Department}\n职位:{emp.Position}\n入职日期:{emp.HireDate:yyyy-MM-dd}\n薪资:{emp.Salary:C2}", "员工信息", MessageBoxButtons.OK, MessageBoxIcon.Information); } } }记得在设计器中将dataGridView1的CellContentClick事件与这个方法关联起来。
6.2 处理单元格值变更(编辑后)
当用户在DataGridView中直接编辑单元格时,我们需要捕获这个变化并更新底层数据。
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) { // 当单元格的值被用户改变并提交后触发(例如,编辑完离开单元格) if (e.RowIndex >= 0 && e.ColumnIndex >= 0) { DataGridView dgv = sender as DataGridView; // 注意:此时DataBoundItem可能已经是最新值,因为绑定是双向的(如果属性实现了INotifyPropertyChanged则更好)。 // 但为了保险,我们可以显式更新。 // 如果绑定的是BindingSource,通常会自动处理。 // 这里可以记录日志或进行数据验证。 Console.WriteLine($"单元格 [{e.RowIndex}, {e.ColumnIndex}] 的值已更改。"); } }注意:如果希望底层数据对象(如
Employee)的属性变更能自动通知UI更新,需要让Employee类实现INotifyPropertyChanged接口。这对于BindingSource和MVVM模式非常重要。
6.3 处理行选择变化
当用户点击不同的行时,我们可能需要在界面的其他部分(如一个详细信息面板)同步显示当前选中行的内容。
private void dataGridView1_SelectionChanged(object sender, EventArgs e) { if (dataGridView1.CurrentRow != null && dataGridView1.CurrentRow.DataBoundItem is Employee selectedEmp) { // 更新其他控件的显示,例如几个Label或TextBox // lblName.Text = selectedEmp.Name; // lblDept.Text = selectedEmp.Department; // ... } else { // 清空其他控件的显示 } }7. 常见问题与避坑指南
在实际使用中,你肯定会遇到一些“坑”。这里分享几个最常见的。
7.1 数据绑定后,新增/删除行界面不更新
问题:直接对List<T>进行Add或Remove操作后,DataGridView没有变化。根因:List<T>没有实现IBindingList接口,因此不提供列表变更通知。解决方案:
- 使用
BindingSource:如前所述,这是最推荐的方式。将List<T>赋值给BindingSource.DataSource,然后通过BindingSource的方法(Add,Remove,RemoveCurrent)来操作数据。 - 使用
ObservableCollection<T>(WPF/Silverlight风格,WinForms中需额外引用):它实现了INotifyCollectionChanged,能自动通知UI。但在WinForms中,仍需通过BindingSource或自定义绑定逻辑来利用这一特性,不如直接使用BindingSource方便。 - 重置
DataSource:每次操作后,执行dataGridView1.DataSource = null; dataGridView1.DataSource = myList;。简单粗暴,但数据量大时性能差,且会丢失排序、筛选状态和当前选择。
7.2 单元格编辑后,数据没有提交到数据源
问题:用户在单元格里输入了内容,但离开单元格后,绑定的List<T>或DataTable里的值没变。根因:编辑的内容还在DataGridView的缓存中,没有“提交”到绑定的数据源。提交通常发生在行焦点离开或调用EndEdit时。解决方案:
- 在获取数据前,强制结束当前编辑:
dataGridView1.EndEdit(); - 确保数据对象的属性有
setter。 - 对于
DataTable,编辑会自动提交。对于对象绑定,需要触发CellValueChanged事件或手动结束编辑。
7.3 性能问题:加载大量数据时卡顿
问题:绑定成千上万行数据时,界面加载慢,滚动卡顿。根因:DataGridView默认会为每一行数据创建一个DataGridViewRow对象及其单元格,非常消耗资源。解决方案:
- 分页加载:这是最有效的方案。只加载当前页的数据。
- 启用虚拟模式:设置
dataGridView1.VirtualMode = true;。你需要自己实现CellValueNeeded等事件来按需提供数据。这需要较高的编程技巧,但能处理海量数据。 - 关闭自动调整:在绑定数据前,设置
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;并手动设置合理的列宽。避免绑定后控件自动计算所有列宽和行高。 - 使用双缓冲:
dataGridView1.DoubleBuffered = true;(需要通过反射设置,因为该属性是protected的)。这能减少滚动时的闪烁。 - 批量操作时暂停绘制:
dataGridView1.SuspendLayout(); // ... 进行大量的添加行、修改列等操作 dataGridView1.ResumeLayout();
7.4 如何获取选中行的原始数据对象
这是最常被问到的问题之一。我们已经用到了正确的方法:
// 获取当前选中行 DataGridViewRow selectedRow = dataGridView1.CurrentRow; if (selectedRow != null && selectedRow.DataBoundItem != null) { Employee emp = selectedRow.DataBoundItem as Employee; // 使用 emp... } // 获取所有选中行(当MultiSelect=true时) foreach (DataGridViewRow row in dataGridView1.SelectedRows) { if (row.DataBoundItem is Employee emp) { // ... } }绝对不要通过遍历单元格文本 (row.Cells["Name"].Value.ToString()) 来重新构造对象,这既低效又容易出错。
7.5 合并单元格、自定义单元格绘制等高级需求
DataGridView本身不支持直接的单元格合并。实现合并通常需要:
- 重写
CellPainting事件,自己绘制背景和边框,模拟合并效果。 - 或者,使用第三方开源控件库(如
DevExpress,Telerik,Syncfusion的网格控件),它们内置了合并功能。
自定义单元格绘制(例如,在单元格内画进度条、图表)也是通过CellPainting事件来实现的,这需要一定的GDI+绘图知识。
8. 项目实例完整串联与扩展思考
让我们把上面的知识点串联起来,形成一个可运行的迷你员工管理系统。核心步骤回顾:
- 建项目,拉控件:创建WinForm项目,放置
DataGridView和按钮。 - 定模型:创建
Employee类。 - 配列:在窗体加载时,关闭自动生成列,手动创建并配置好每一列(标题、宽度、格式)。
- 绑数据:创建
BindingSource,将Employee列表绑定到它,再将BindingSource绑定到DataGridView。 - 写样式:设置交替行颜色、列头样式、选择模式等。
- 做功能:为四个按钮编写事件处理程序,通过操作
BindingSource或List<T>来实现增删改查,并通过FormEmployeeEdit对话框进行数据编辑。 - 加交互:处理
DataGridView的CellContentClick事件响应按钮列,处理SelectionChanged事件更新状态。
扩展思考:
- 数据持久化:将
employeeList替换为真正的数据库访问(如使用Entity Framework Core或Dapper)。按钮的点击事件中,操作将变为对数据库的增删改查,然后重新绑定到DataGridView。 - 排序与过滤:
BindingSource天然支持排序 (Sort属性) 和过滤 (Filter属性)。你可以轻松实现点击列头排序,或添加一个文本框实现实时过滤。 - 导出数据:可以添加功能,将
DataGridView中的数据导出为 Excel (EPPlus库)、CSV 或 PDF 文件。 - 分页:当数据量很大时,实现一个分页控件,结合数据库查询的
SKIP和TAKE语句,每次只加载一页数据到DataGridView。
DataGridView是一个深度非常深的控件,本文涵盖的只是其基础应用的核心部分。但掌握了数据绑定、样式配置、事件处理和BindingSource的使用,你已经能够应对绝大多数常见的业务表格开发需求。记住,遇到复杂需求时,多查官方文档,善用智能感知,并理解其“数据绑定”的核心思想,很多问题都会迎刃而解。