datagridview控件如果是选中了新行,一旦其他控件获得焦点就自动选了最后一行

张开发
2026/6/11 14:30:06 15 分钟阅读

分享文章

datagridview控件如果是选中了新行,一旦其他控件获得焦点就自动选了最后一行
如果是选中了新行一旦其他控件获得焦点比如BUTTON1 2026/6/11 0:30:03--e.RowIndex9,IsNewRowTrue 2026/6/11 0:30:04--e.RowIndex8,IsNewRowFalse 系统会自动选中最后一行只是触发了事件但表面上没看出来已完成思考这是 DataGridView 存在了 15 年的幽灵焦点 官方 bug**而且是绑定数据源模式专属当你点击新增行带 *后再点击任何其他控件按钮、文本框、菜单DataGridView 会偷偷在后台把内部焦点从新增行跳到最后一行真实数据触发RowEnter事件但UI 界面完全不变看起来还是选中新增行。为什么会这样微软的设计缺陷新增行是虚拟行不是真正的 DataRow不能持有持久焦点当 DataGridView 失去焦点时微软的代码会强制将焦点 修正 到一个真实存在的 DataRow但这个修正只改了内部状态没有更新 UI 高亮、没有滚动条所以你完全看不出来这就是为什么你会看到日志e.RowIndex9→ 点击按钮 →e.RowIndex8但界面没变化一行代码彻底修复永久禁用自动跳转在窗体构造函数或 Load 事件里加这一行从根源上禁止自动跳转Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 【核心修复】禁止失去焦点时自动跳转到最后一行 DataGridView1.StandardTab True End Sub✅ 这个属性是微软专门为这个 bug 留的后门设置为True后点击新增行再点击其他控件不会再自动跳转到最后一行RowEnter事件不会再被错误触发所有焦点行为完全符合预期不影响任何其他功能包括新增、编辑、删除配合你的 RowEnter 事件最终完整代码 全局变量存当前选中的ID任何地方都能取 Private currentSelectedID As Integer 0 Private isFormLoaded As Boolean False Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 【核心修复】禁止失去焦点时自动跳转到最后一行 DataGridView1.StandardTab True 其他初始化代码... isFormLoaded True End Sub Private Sub DataGridView1_RowEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter If Not isFormLoaded Then Exit Sub If e.RowIndex 0 Then currentSelectedID -1 Exit Sub End If 1. 底部带*的新增行 → 返回0 If e.RowIndex DataGridView1.NewRowIndex Then currentSelectedID 0 Debug.Print($[{DateTime.Now:HH:mm:ss}] 选中新增行ID0) Exit Sub End If 2. 未保存的数据源新增行 → 返回0 Dim drv TryCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView) If drv IsNot Nothing AndAlso drv.Row.RowState DataRowState.Added Then currentSelectedID 0 Debug.Print($[{DateTime.Now:HH:mm:ss}] 选中未保存新增行ID0) Exit Sub End If 3. 正常数据行 → 读取ID Dim val As Object DataGridView1.Rows(e.RowIndex).Cells(ID).Value currentSelectedID If(IsDBNull(val) OrElse val Is Nothing, 0, CInt(val)) Debug.Print($[{DateTime.Now:HH:mm:ss}] 选中行索引{e.RowIndex}ID{currentSelectedID}) End Sub如果 StandardTab 不生效极少数情况用这个兜底方案手动保存最后一次选中的行号完全忽略自动跳转的事件Private lastValidRowIndex As Integer -1 Private Sub DataGridView1_RowEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter If Not isFormLoaded Then Exit Sub If e.RowIndex 0 Then Exit Sub 【兜底修复】忽略失去焦点时的自动跳转 If Not DataGridView1.Focused Then DataGridView没有焦点时所有RowEnter事件都是假的直接用上次保存的值 e.Cancel True 取消这次无效的行切换 Exit Sub End If 保存有效的行号 lastValidRowIndex e.RowIndex 后面的判断逻辑和上面完全一样... End Sub验证方法点击新增行索引 9日志显示ID0点击 Button1 按钮不会再触发任何 RowEnter 事件再点击 DataGridView焦点还是在新增行日志正常显示ID0点击最后一行真实数据索引 8日志显示正确的 ID补充说明这个 bug 在.NET 2.0 到.NET 10 全版本存在微软一直没修StandardTab True还有一个副作用按 Tab 键会离开 DataGridView而不是在单元格间循环这其实是更符合用户习惯的行为如果你一定要保留 Tab 键在单元格间循环的行为就用第二个兜底方案

更多文章