
简介面向使用 C# 进行 Windows Forms 开发的初中级开发者这份 DataGridView 直接修改数据示例包以可运行的 Visual Studio 解决方案形式演示在表格控件中完成编辑、校验、同步数据源的典型流程。内容覆盖编辑模式设置、单元格开始编辑、结束编辑、校验等事件处理输入合法性验证、提交更改、日期与下拉等自定义编辑控件以及行添加、行删除、异常处理等实践要点适合需要快速上手 DataGridView 数据交互的读者参考复用。资源包为 RAR 压缩格式共 28 个文件以 9 个 cs 源文件及工程配置为主另含可执行文件、调试符号、资源与设置文件整体仅 53KB结构简洁便于直接编译查看。目前已有 918 人学习下载通过该示例可直观看到从界面编辑到数据源更新的完整调用关系并可将其中的验证与同步逻辑迁移到自己的项目表格模块中。1. 一句话说清“DataGridView 直接修改数据”到底解决什么问题做过 WinForms 信息管理系统的工程师基本都经历过这类“需求”客户看着 DataGridView 里加载的列表问能不能像 Excel 一样鼠标点进去直接改改完保存不用弹窗、不用表单。这个需求在 C/S 架构的老项目里很常见而 DataGridView 直接修改数据指的就是用 DataGridView 自带的单元格编辑能力把表格本身变成录入界面。用户单击单元格进入编辑态修改后回车或鼠标移开值就写回绑定的 DataTable、BindingSource 或实体集合再做一次批量保存即可入库。它能省掉大量“表单页 数据绑定 弹窗编辑”的重复代码适合单据明细、配置项维护、批量修正数据这类交互简单的场景。不过“能打字”和“能真正把值改进去”是两回事。DataGridView 默认允许输入但编辑后的数据不会自动回到数据源也不会自动覆盖数据库中的旧值。标题里的“直接修改数据”重点不在编辑框怎么弹出来而在于从用户输入到数据源更新、再到数据持久化这一整条链路怎么打通。下面从默认行为讲起再逐层把这条链路拆开。2. 从“能打字”到“数据回写”DataGridView 的单元格编辑机制与最小可跑示例2.1 为什么 DataGridView 默认能输入数据却没被改进去DataGridView 的编辑能力由它的 EditMode 属性控制常见取值有三个EditOnEnter 表示单元格获得焦点就进入编辑态EditOnKeystroke 表示用户按下 F2 或直接键入字符时才进入编辑态EditProgrammatically 表示只有代码里调用 BeginEdit 才允许编辑。默认值是 EditOnKeystroke这就是为什么表格加载数据后你可以在单元格里直接打字但关掉窗口、重新加载数据改动没了。真正的问题出在数据回写路径上。DataGridView 显示数据时通常通过 DataSource 属性绑定一个 DataTable、List 或 BindingSource。用户编辑的是单元格视图而不是数据源本身。编辑结束后DataGridView 会触发 CellEndEdit 和 CellValueChanged 事件此时需要调用 BindingSource 的 EndEdit或者直接操作 DataRow 的对应列才能把 UI 上的值真的写进内存数据源。如果跳过了这一步单元格显示的是新值底层 DataRow 里还是旧值保存时自然什么也没改。提示CellValueChanged 事件在编辑提交后触发但此时数据源可能还未更新。务必保证编辑结束流程里先让 DataGridView 完成提交再读取数据源内容。2.2 最小可跑示例直接绑定 DataTable 并完成单元格修改最常见的做法是直接把 DataTable 作为 DataSource利用 DataGridView 自带的编辑能力配合 BindingSource 做中间层。BindingSource 的作用类似一个给 DataGridView 和 DataTable 之间传话的中间人它负责协调当前行、EndEdit 提交顺序以及后续的绑定操作。下面这一段是我常用的最小实现。窗体上放一个 DataGridView 和一个“保存修改”按钮加载数据后允许用户直接编辑单元格点按钮时一次性把改动写回数据库。private DataTable _table; private void Form1_Load(object sender, EventArgs e) { // 1. 准备内存数据表模拟从数据库查出来的明细数据 _table new DataTable(); _table.Columns.Add(Id, typeof(int)); _table.Columns.Add(ProductName, typeof(string)); _table.Columns.Add(Quantity, typeof(int)); _table.Columns.Add(UnitPrice, typeof(decimal)); _table.Rows.Add(1, 螺丝 M4, 200, 0.15m); _table.Rows.Add(2, 螺母 M4, 300, 0.12m); _table.Rows.Add(3, 垫圈, 500, 0.05m); // 2. 用 BindingSource 包一层后续 EndEdit 由它统一协调 BindingSource bs new BindingSource(); bs.DataSource _table; // 3. 设置 DataGridView 的编辑模式为“按回车或按键编辑”并绑定数据源 dataGridView1.EditMode DataGridViewEditMode.EditOnKeystrokeOrF2; dataGridView1.DataSource bs; } private void btnSave_Click(object sender, EventArgs e) { // 1. 先让 DataGridView 结束当前编辑会话确保用户正在编辑的单元格内容已提交到绑定源 dataGridView1.EndEdit(); // 2. 再让 BindingSource 结束编辑把值同步回 DataTable BindingSource bs dataGridView1.DataSource as BindingSource; bs.EndEdit(); // 3. 校验并遍历每一行生成更新语句 foreach (DataRow row in _table.Rows) { // 只对状态为 Modified 的行做更新 if (row.RowState DataRowState.Modified) { // 这里执行 UPDATE 语句参数值从 row 各列取值 // 例如UPDATE Products SET Quantityqty, UnitPriceprice WHERE Idid } } }这段代码里最容易出问题的顺序是dataGridView1.EndEdit()和bs.EndEdit()谁先谁后。我一般先让 DataGridView 结束编辑态因为 DataGridView.EndEdit 只会结束 UI 上的单元格输入不会自动把值写进 DataTable紧接着调用 BindingSource.EndEdit它会把当前行的修改提交到 DataTable此时 DataRow 的 RowState 会变成 Modified最后遍历时按状态筛选就有据可依。参数层面EditMode 用了EditOnKeystrokeOrF2效果是用户直接打字或按 F2 都可以进入编辑如果想做到“鼠标点进去就改”就把 EditMode 改为EditOnEnter。两种模式没有绝对好坏EditOnEnter 在键盘录入较多的场景里容易误触EditOnKeystrokeOrF2 更贴近 Excel 的操作习惯。2.3 不经过 BindingSource直接在 CellEndEdit 里手动写回数据源有些老项目没引 BindingSourceDataSource 直接绑的是 DataTable 或 List 。此时需要在 CellEndEdit 事件里手动取值并写回。DataGridView 的 CurrentRow.DataBoundItem 可以拿到当前行绑定的原始对象它是 DataRowView 或实体对象。下面的写法适用于绑定 DataTable 的场景简单直接适合把编辑逻辑集中在一个事件里。private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) { // 忽略列头行和新行 if (e.RowIndex 0 || dataGridView1.Rows[e.RowIndex].IsNewRow) return; // 1. 从当前单元格所在的列名判断要写哪一列 string columnName dataGridView1.Columns[e.ColumnIndex].DataPropertyName; // 2. 取出绑定到这一行的 DataRowView DataRowView drv dataGridView1.Rows[e.RowIndex].DataBoundItem as DataRowView; if (drv null) return; // 3. 直接用用户输入的新值覆盖 DataRow 对应列 object newValue dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value; drv.Row[columnName] newValue; }这个事件写法的前提是DataGridView 已经设置了每个列的 DataPropertyName让列的显示与 DataTable 的列名一一对应。如果 DataPropertyName 为空列名就和绑定字段对不上第二行代码取出来的 columnName 不能用。建议在窗体设计器里给每一列都设置 DataPropertyName而不是依赖自动生成列。直接在 CellEndEdit 里写回的做法适合改动逻辑简单、不需要统一事务提交的小工具。但它的短板也很明显用户在一个单元格里改了值失焦后立刻被写回 DataRow如果最后决定放弃所有修改内存里已经被污染。此时必须通过 DataTable.RejectChanges 来还原。这是它的“后悔药”代价是对每个单元格的写回都得做一次类型转换容易在边界场景触发异常。2.4 三种实现方式的对比与选型实现方式数据回写机制适用场景放弃修改的还原方式DataSource 直接绑定 DataTable编辑后 DataTable 自动感知状态单机小工具、快速原型RejectChangesBindingSource 包一层EndEdit 后同步回 DataTable中大型窗体、需要批量保存校验RejectChanges 或重建 BindingSource.DataSourceCellEndEdit 手动写回事件里自行赋值列多、字段映射特殊、需要逐格处理需自行备份修改前值选型时我一般遵循一个经验凡是界面里有“保存”按钮的用 BindingSource 包一层凡是改一个单元格立即生效、不需要批量保存的用 CellEndEdit 手动写回凡是直接在 DataSource 上绑 DataTable 又不用 BindingSource 的适合只读列表不适合“直接修改数据”。3. 把单元格变成真正的编辑器编辑模式、列类型与 ReadOnly 的三个关键设置3.1 列类型如何影响“能不能直接改”DataGridView 的单元格编辑能力并不只靠 EditMode。每一列还有自己的 CellTemplate它决定了编辑控件是什么。默认的 DataGridViewTextBoxColumn 提供的是文本框能输入任意字符串DataGridViewComboBoxColumn 提供下拉列表绑定数据后只能从列表项中选择DataGridViewCheckBoxColumn 提供复选框适合布尔类型字段DataGridViewDateTimeColumn 适合日期录入。如果绑定字段是 decimal 或 int但列类型是 DataGridViewTextBoxColumn那用户输入“abc”会触发 CellFormatting 和 DataError 事件因为 DataGridView 把字符串转回目标类型时失败了。所以列类型要与字段类型匹配而不是全部用文本框应付。对于“直接修改数据”这种场景列类型不匹配是新手最容易踩的天坑后面会专门展开。3.2 锁定不该被改的列ReadOnly 的三种粒度不是每列都需要直接修改。订单编号、流水号、创建时间这些字段不该让用户碰需要锁定。ReadOnly 的设置有三个粒度列级dataGridView1.Columns[OrderId].ReadOnly true;只锁定这一列。行级dataGridView1.Rows[0].ReadOnly true;整行不可编辑适合锁定已经审核的单据行。单元格级dataGridView1.Rows[0].Cells[Quantity].ReadOnly true;按单元格条件锁定适合“行状态为已审核则不可改数量”这类业务规则。用列级或单元格级 ReadOnly 时有一个隐性行为要留意ReadOnly 为 true 的单元格进入编辑态时不会触发 CellBeginEdit 事件但用户依然可以通过快捷键触发编辑请求此时界面没有反应。要避免这种情况可以在 CellBeginEdit 里再拦截一次弹提示说明为什么这一列不能改。private void dataGridView1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e) { DataGridView dgv sender as DataGridView; // 1. 例如“单价”这一列当行状态为已审核时不允许修改 string rowStatus dgv.Rows[e.RowIndex].Cells[Status].Value?.ToString(); if (dgv.Columns[e.ColumnIndex].Name UnitPrice rowStatus Approved) { MessageBox.Show(已审核单据不允许修改单价); e.Cancel true; // 取消本次编辑 } }这个小节用一个事件就把只读状态拦得严严实实。注意e.Cancel true必须在进入编辑态之前完成一旦进入编辑态再设置 ReadOnly 就来不及了。3.3 行头“小铅笔”图标的含义与编辑状态的视觉反馈DataGridView 左侧的行头有个小铅笔图标它标出当前处于编辑状态的行。用户改了某个单元格后行头的小铅笔会变成一个“信号灯”图标表示这一行有修改未保存。这个视觉反馈在直接修改数据时特别重要因为用户需要知道当前“有没有改动、改在哪些行”。如果你希望行头更直观地区分“修改中”和“已修改”可以设置 RowHeadersWidth 和 RowHeadersBorderStyle 让行头区域更明显也可以给 DataGridView 挂一个 CellValueChanged 事件在行头单元格写一个标记。我一般不去覆盖默认图标而是用 CellValueChanged 把修改行的背景色改成浅黄色用户一眼能看出哪几行动过保存后再统一恢复默认颜色。这个小技巧在批量修改场景里很实用。4. 从 UI 到数据库数据校验、批量保存与事务边界4.1 校验时机选择为什么不要在键入过程中弹 MessageBox“直接修改数据”意味着用户输入的数据会直接写回数据源如果不校验垃圾数据就入库了。校验时机有两种可选单元格编辑结束时校验或点保存时统一校验。两者不能混着用否则会弹窗弹到崩溃。如果在 CellEndEdit 里弹 MessageBox 提示“数量不能为负”用户每改一个单元格就弹一次窗体验极差。正确的做法是在 CellEndEdit 里做快速校验能拦截的类型问题直接拦截比如 int 列输入了非数字而业务规则校验比如数量不能超过库存放到点保存时统一做。快速校验用 DataGridView.DataError 事件和 CellValidating 事件处理。CellValidating 是 DataGridView 内置的校验事件它在单元格结束编辑前触发e.Cancel true 时编辑不会结束焦点也移不走。这个事件特别适合拦截格式错误private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { // 1. 跳过新行和无绑定的行 if (dataGridView1.Rows[e.RowIndex].IsNewRow) return; // 2. 只对 Quantity 列校验 if (dataGridView1.Columns[e.ColumnIndex].Name Quantity) { int parsed; if (!int.TryParse(e.FormattedValue.ToString(), out parsed) || parsed 0) { MessagePanel.Show(数量必须是大于等于 0 的整数); e.Cancel true; dataGridView1.Rows[e.RowIndex].ErrorText 数量不合法; } } } private void dataGridView1_CellValidated(object sender, DataGridViewCellEventArgs e) { // 3. 校验通过后清掉之前挂在行上的错误提示 dataGridView1.Rows[e.RowIndex].ErrorText string.Empty; }4.2 批量保存遍历 Modified 行还是全量 Update拿到用户修改后的数据后保存方式决定了事务边界和使用体验。常见保存策略有三种按行状态增量更新只更新 RowState 为 Modified 的行SQL 是 UPDATE按主键定位。全量重建把 DataTable 里所有行先 DELETE 再 INSERT适合子表明细比如先删后插订单明细。SqlDataAdapter.Update 自动同步把 SqlCommandBuilder 生成的更新命令挂到 Adapter 上一行代码提交。第三种方案看起来省事但 SqlCommandBuilder 生成的 UPDATE 命令会把所有列都放进 SET 子句而且并发冲突处理很弱生产环境基本不推荐。我用的最多的是第一种点保存时把 DataTable 的 Changes 取出来遍历 Modified 行逐条拼 SQL 或调存储过程同一个 SqlTransaction 里提交。private void btnSaveChanges_Click(object sender, EventArgs e) { // 1. 获取当前 DataGridView 绑定的 BindingSource先结束编辑 BindingSource bs dataGridView1.DataSource as BindingSource; dataGridView1.EndEdit(); bs.EndEdit(); // 2. 从 DataTable 中取出有改动的行 DataTable changes _table.GetChanges(DataRowState.Modified); if (changes null || changes.Rows.Count 0) { MessagePanel.Show(没有需要保存的修改); return; } // 3. 打开连接和事务逐行执行更新 using (SqlConnection conn new SqlConnection(_connStr)) { conn.Open(); SqlTransaction tx conn.BeginTransaction(); foreach (DataRow row in changes.Rows) { using (SqlCommand cmd conn.CreateCommand()) { cmd.Transaction tx; cmd.CommandText UPDATE Products SET ProductName name, Quantity qty, UnitPrice price WHERE Id id; cmd.Parameters.AddWithValue(name, row[ProductName]); cmd.Parameters.AddWithValue(qty, row[Quantity]); cmd.Parameters.AddWithValue(price, row[UnitPrice]); cmd.Parameters.AddWithValue(id, row[Id]); cmd.ExecuteNonQuery(); } } tx.Commit(); _table.AcceptChanges(); // 4. 提交成功后重置行状态 MessagePanel.Show(保存成功); } }这段代码里有一个我踩过三四次的坑_table.GetChanges()必须在bs.EndEdit()之后调用否则用户正在编辑的当前行还没有被写入 DataTable取出来的 changes 里少一行。第二个坑是参数类型AddWithValue对 decimal 和 int 的判定准不准取决于 C# 变量的实际类型如果 DataTable 里的列类型是 decimal而 row[Quantity] 因为 DBNull.Value 被 AddWithValue 推断成了 objectSQL Server 会报“不允许参数 qty 隐式转换”之类的错误。更稳妥的做法是显式指定 SqlDbType。4.3 什么时候该用 DataGridView 内置的错误图标DataGridView 的 ErrorText 能在行头和单元格里显示一个红色感叹号图标鼠标悬停能看到错误信息。这是“直接修改数据”场景里最自然的错误提示方式不必弹窗不影响用户继续编辑。每次编辑后把行级错误写到 ErrorText保存时如果发现某行 ErrorText 非空就定位到该行并阻止提交。这个内置机制能省掉一个“错误列表面板”特别适合一个窗体里同时编辑多行数据的场景。它的限制是错误信息只在悬停时显示如果错误很多用户不一定逐行去悬停。所以我通常把校验错误分为两类格式错误在 CellValidating 里拦截业务错误写入 ErrorText保存时统一提示“有 N 行未通过校验”并把第一行出错的行滚动到可视区域这样用户能立刻找到问题所在。5. DataGridView 直接修改数据的避坑清单5 个血泪经验5.1 问题一编辑后回车值显示改了数据库没变现象用户修改单元格按回车后界面上显示的是新值点保存后数据库里还是旧值。原因回车只是结束了单元格编辑态值回到了 DataGridView 的缓存但 BindingSource 没调用 EndEditDataTable 里的 DataRow 未被更新。解决保存按钮第一行必须调用dataGridView1.EndEdit()紧接着调用BindingSource.EndEdit()顺序不能反。我见过不少同事只调了后者结果当前编辑的单元格因为在 EndEdit 触发前就丢失了焦点值其实没进 DataTable。5.2 问题二CellEndEdit 里取 CurrentRow.DataBoundItem 为 null现象在 CellEndEdit 事件里写dataGridView1.CurrentRow.DataBoundItem as DataRowView经常拿到 null。原因用户点击单元格进入编辑此时 CurrentRow 指向的是最后鼠标点击的行而事件触发的行可能已经因为用户点击了列头排序而改变还有一种情况是行处于未绑定状态例如 AllowUserToAddRows 产生的新行。解决事件签名里的e.RowIndex才是真正的编辑行索引用dataGridView1.Rows[e.RowIndex]取绑定对象不要在事件里依赖 CurrentRow。新行通过IsNewRow排除避免对占位行做数据操作。5.3 问题三DataError 事件刷屏Type 转换异常满天飞现象用户在某列输入了非法字符或数据库中字段是 int用户输入了空字符串DataGridView 弹出 DataError 事件有时连续弹三四次。原因DataGridView 在把单元格显示值转换回绑定字段类型时失败。解决在 DataError 事件里做一个降级处理判断 e.Exception 是不是 FormatException如果是格式错误把单元格背景标红并忽略本次编辑不要默认弹 MessageBox。同时检查绑定列的 ValueType 是否与 DataTable 对应列一致绑 List 时尤其容易忽略泛型类型。5.4 问题四AllowUserToAddRows 带来一个“幽灵新行”现象DataGridView 底部有一个空行允许用户新增。但遍历 Rows 时最后一行经常抛出 RowState 异常或取值时拿到 DBNull。原因AllowUserToAddRows 为 true 时DataGridView 会额外维护一个未绑定的新行占位符它在数据源里并不存在。解决遍历之前先判断dgv.Rows[i].IsNewRow为 true 则跳过批量保存前如果用户没录入数据调用CancelEdit()取消这个新行或者直接把 AllowUserToAddRows 设为 false只允许修改已有数据。5.5 问题五当前编辑单元格的值在 CellValueChanged 里还是旧值现象在 CellValueChanged 事件里拦截修改判断新旧值是否变化但发现拿到的 Value 是旧值。原因事件触发时值已经更新但你可能读取的是dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value的绑定值而不是编辑控件的 Text。如果 DataGridView 还没完成内部提交此时读取的数据与编辑框内显示的不一致。解决在 CellEndEdit 之后再去读取单元格值不要在 CellValueChanged 里做“读取新值、回写数据源”的操作如果需要拿用户刚输入的内容CellValidating 事件的e.FormattedValue才是编辑框里的原始文本。提示上述 5 个问题基本覆盖了直接修改数据的 80% 踩坑场景。遇到异常时打开 Visual Studio 的异常设置勾选“引发 Common Language Runtime 异常”DataGridView 内部的很多隐蔽问题会第一时间暴露出来。6. 进阶让“直接修改”真正丝滑的 5 个细节6.1 用 Enter 键向下跳转而不是停在原单元格默认情况下用户在一个单元格按回车焦点会停留在原单元格或向右移动这对连续的列录入非常不友好。把回车改成“确认当前值并跳到下一行同一列”录入体验会接近 Excel。做法是重写 ProcessDialogKey 或处理 KeyDown 事件判断 KeyCode 为 Enter 时EndEdit 后把 CurrentCell 设为下一行同一列。注意必须设置e.Handled false否则系统默认的焦点移动逻辑会覆盖你的跳转。6.2 文本列的可视化反馈单元格边框和背景色直接修改数据的界面里用户最怕的是“不知道改了哪一行”。我在 CellValueChanged 里统一给修改过的行设置背景色Color.FromArgb(255, 255, 230)保存成功后再把颜色恢复成默认白色。这个方案比行头铅笔图标更直接因为背景色更好辨认。注意不要用太深的颜色否则用户看不清文字也别在 DataGridView 开启自定义背景色的同时再设置单元格样式容易覆盖 DataGridViewDefaultCellStyle。6.3 下拉列直接修改ComboBox 列怎么只存 Id 而显示名称业务上经常出现“显示客户名称但要存 CustomerId”的情况。DataGridViewComboBoxColumn 的 DataSource 绑定一个“客户名-Id”对照表DisplayMember 设为“客户名”ValueMember 设为“Id”。这样用户在下拉框里直接选客户名DataGridView 存储的却是 Id修改后触发 CellValueChanged取到的 Value 就是 Id。这个方案避免了自己写 TextChanged 事件去翻译名称和 Id。// 1. 准备好客户字典 DataTable customerTable new DataTable(); customerTable.Columns.Add(CustomerId, typeof(int)); customerTable.Columns.Add(CustomerName, typeof(string)); customerTable.Rows.Add(101, 上海仪器厂); // 2. 在 DataGridView 里创建下拉列 DataGridViewComboBoxColumn colCustomer new DataGridViewComboBoxColumn(); colCustomer.HeaderText 客户; colCustomer.DataSource customerTable; colCustomer.DisplayMember CustomerName; // 显示名称 colCustomer.ValueMember CustomerId; // 实际存储 Id colCustomer.DataPropertyName CustomerId; // 绑定到 DataTable 的哪一列 dataGridView1.Columns.Add(colCustomer);6.4 验证保存结果时如何精确区分“改了但没保存”和“保存后又被改”这个场景很常见用户改了几行保存成功再改另几行再次保存。如果 DataTable 的状态没有随保存而重置第二次保存会把之前已经提交的行也一起 Update。所以每次保存成功后必须调用_table.AcceptChanges()把 DataRow 的 RowState 全部重置为 Unchanged。如果不重置第二次点保存时 GetChanges(DataRowState.Modified) 会把第一次已经改过的行再次取出来数据库莫名其妙被 Update 两次。6.5 数据源是 List 而不是 DataTable 时的特殊处理DataGridView 直接修改数据最常见的绑定对象是 DataTable但有些项目是用 List 加载数据的。List 不实现 IBindingList 的增删通知DataGridView 对它默认是只读的。直接修改数据时最好是给 List 套一层 BindingList 或者提前把 List 转成 DataTable。我一般用 BindingList 作为中介修改后逐个属性赋值或用反射做属性映射。如果项目里大量使用 List 建议封装一个小工具方法把 List 转成 DataTable 再绑定否则 CellEndEdit 里取 DataBoundItem 是 List 的元素类型转换代码会散落到各个窗体里。实际操作里我这几年做 WinForms 数据维护类的功能大部分精力不是耗在 DataGridView 本身而是耗在“什么时候值回写了、什么时候该保存、有没有校验漏网”这三件事上。DataGridView 只是一个交互入口真正决定这个功能好不好用的是你在编辑提交、数据校验、批量保存这条链路上有没有把每个时机的坑都填平。希望这篇能把“直接修改数据”这条路的轮廓讲清楚你按上面的代码走一遍再结合自己项目的表结构基本就能把老项目的弹窗编辑换成一整套直接在表格里改的可维护方案了希望帮到你。本文还有配套的精品资源点击获取