设置 DataSource 属性时无法修改 DatagridviewCombobox Items 集合

问题描述

我有一个使用 VB.NET 的 Windows 窗体应用程序,旨在升级 IIS Web 应用程序。

该应用程序有一个 datagridview,显示升级的网络应用程序列表。其中一列是 DataGridViewComboBoxColumn。此组合框将其 DataSource 属性设置为数据表。我没有为组合框使用绑定源。

这个想法是用户从 datagridview 行中的组合框中选择一个值,并更新同一 datagridview 行中的其他几个单元格的值。

EditingControlShowing 事件处理程序设置为设置组合框的 SelectedindexChanged 事件。

Private Sub dgvWebApps_EditingControlShowing(sender As Object,e As DataGridViewEditingControlShowingEventArgs) Handles dgvWebApps.EditingControlShowing
    If dgvWebApps.CurrentCell.ColumnIndex = 5 Then
        Dim comboBox As ComboBox = CType(e.Control,ComboBox)
        If comboBox IsNot nothing Then
            RemoveHandler comboBox.SelectedindexChanged,AddressOf ComboBox_Value_Changed
            AddHandler comboBox.SelectedindexChanged,AddressOf ComboBox_Value_Changed
            e.CellStyle.BackColor = clrLightYellow
        End If
    End If
End Sub

ComboBox_Value_Changed 是更新其他单元格值的子程序。

Private Sub ComboBox_Value_Changed(sender As Object,e As EventArgs)
    dgvWebApps.CurrentRow.Cells(frmMain.cnstNewVersion).Value = GetNewVersion(sender.Text)
    dgvWebApps.CurrentRow.Cells(frmMain.cnstSourcePath).Value = strSourcePath
    dgvWebApps.CurrentRow.Cells(frmMain.cnstSourceMediaFileUpdated).Value = True
    dgvWebApps.CurrentCell = dgvWebApps.CurrentRow.Cells(frmMain.cnstSourceMediaFile)
End Sub

还要处理 datagridview 的 CurrentCellDirtyStateChanged 事件,以便在 datagridview 的 EditMode 属性设置为 EditOnEnter 时立即提交当前编辑,并更新当前 datagridview 行中的单元格值。

Private Sub dgvWebApps_CurrentCellDirtyStateChanged(sender As Object,e As EventArgs) Handles dgvWebApps.CurrentCellDirtyStateChanged
    If dgvWebApps.CurrentCell.ColumnIndex = 1 Then
        dgvWebApps.CurrentRow.Cells(frmMain.cnstLocalPath).Value = Path.Combine(strWebsitePhysicalPath,dgvWebApps.CurrentCell.Value)
    End If
    If dgvWebApps.IsCurrentCellDirty Then
        dgvWebApps.CommitEdit(DataGridViewDataErrorContexts.Commit)
    End If
End Sub

这通常工作正常。我可以从 datagridview 的组合框中选择不同的值,其他单元格会相应地更新,同时移动到不同的单元格。

我遇到的问题是用户更改组合框的值,然后将表单上的焦点更改为 datagridview 以外的其他内容,然后单击回组合框之一。这是当 datagridview 的 DataError 事件被触发时,我得到了一些相同的错误

这是错误

错误消息:当设置了 DataSource 属性时,无法修改项目集合。

错误堆栈跟踪: 在 System.Windows.Forms.ComboBox.CheckNoDataSource() 在 System.Windows.Forms.DataGridViewComboBoxCell.InitializeEditingControl(Int32 rowIndex,Object initialFormattedValue,DataGridViewCellStyle dataGridViewCellStyle) 在 System.Windows.Forms.DataGridView.InitializeEditingControlValue(DataGridViewCellStyle& dataGridViewCellStyle,DataGridViewCell dataGridViewCell)

错误显然意味着在设置数据源属性后组合框的项目集合正在被修改。但是,代码中没有任何地方添加删除或清除组合框的项目集合。

如果我继续处理所有错误,或者只是在数据网格的 DataError 事件期间没有弹出任何消息,那么一切似乎都应该发生。我可以继续从组合框等中选择值。但是,如果我再次改变焦点并返回组合框,错误会再次发生。

据我所知,导致错误的是 ComboBox_Value_Changed 子例程期间其他单元格的值更改。删除这些会使错误消失。

现在,如果我对组合框使用 SelectionChangeCommitted 事件而不是 SelectedindexChanged 事件来调用 ComboBox_Value_Changed 子例程,则不会发生此错误。这是我最初使用的事件,但它并没有随着表单上的某些用户交互而始终如一地引发。这就是为什么我在对这些行为进行了一些研究后切换到 SelectedindexChanged 事件。

我不确定为什么在这些情况下会修改组合框的项目集合。如前所述,我可以只处理 datagridview 的 DataError 事件而不生成任何消息,一切似乎都按预期工作。如果可能的话,我只想知道这里发生了什么。如果不需要的话,我不喜欢在错误发生时抑制它们。

解决方法

首先,我强烈建议您在“编译”选项中打开“选项严格”。您当前的代码不会在其当前状态下编译。似乎有一些幕后演员正在进行,这可能会出现问题。在任何情况下,打开“Option Strict”选项都是很好的做法。

我相信您的问题在于,SelectedIndexChanged 事件中创建的 ComboBox 订阅的 EditingControlShowing 事件永远不会取消订阅。这将导致 ComboBox_SelectedIndexChanged 事件在不应触发时触发。如果没有取消订阅,我相信该事件会被触发更多次我们需要或在这种情况下想要的。因此,当用户“离开”组合框单元格时,您需要取消订阅组合框事件。

幸运的是,有一个简单的解决方案可用。首先,我们需要“全局”访问在 ComboBox 事件 EditingControlShowing 中创建的 comboBox。因此,将其设为“全局”ComboBox 变量...

Dim comboBox As ComboBox

然后将 comboBox 事件中的 EditingControlShowing 代码分配更改为类似……

comboBox = CType(e.Control,ComboBox)

现在我们可以“全局”访问 comboBox,我们可以从网格 ComboBox_SelecedIndecChanged 事件中的 CellLeave 事件取消订阅。此事件可能看起来像……

Private Sub dgvWebApps_CellLeave(sender As Object,e As DataGridViewCellEventArgs) Handles dgvWebApps.CellLeave
  If e.ColumnIndex = 5 Then
    RemoveHandler comboBox.SelectedIndexChanged,AddressOf ComboBox_Value_Changed
  End If
End Sub

我相信这会解决您的问题。如果您需要更多详细信息,请告诉我。祝你好运。

根据 OP 评论编辑

启用 Strict 选项对您来说确实是一个好处。它会寻找可能导致问题的东西,而这些问题可能并不明显。

例如,在您当前的代码中,如果您打开“Strict”,它将标记该行...

sender.Text

ComboBox_SelectedIndexChanged 事件中作为一个问题。它将显示一条错误消息,内容类似于……“不允许使用严格选项 ON 延迟绑定。”

这是什么意思,是一个对象自动/在幕后得到 CAST 到另一个对象。特别是 senderComboBox。在强类型语言中,必须显式完成此转换。原因是这对您有帮助。编译器只是说……“嘿……您确定对象 (sender) 会像您期望的那样正确地转换为 ComboBox?”

在这种特殊情况下,sender 是一个 Object……而 Object 没有 Text 属性。因此,编译器将“自动/在幕后”将 sender 对象强制转换/缩小为 ComboBox,因为这显然是 sender

当然,这当然很方便,但是,这种自动/在幕后缩小对象的方式被缩小到您可能不期望的东西的情况并不少见。因此来自编译器的危险信号。强类型语言不会自动为您执行此转换。从编码的角度来看……这是有道理的,我更喜欢这样来确保我的类型正是我所期望的。

在所有情况下,当您遇到使用“Strict”选项引发的错误时,正如我们在此处所做的那样,通过显式执行此转换几乎总是可以轻松修复它们。在这种情况下,要摆脱后期绑定问题,请将代码更改为……

Dim cb As ComboBox = CType(sender,ComboBox)
DataGridView1.CurrentRow.Cells(1).Value = GetNewVersion(cb.Text) 

...此更改将消除错误。在我们开始时,我的观点是,这有助于您避免可能的错误。因此,遵循 ON 编译器接受的“严格”选项是一种很好的做法。此外,打开“Strict”并修复错误将允许代码编译,即使“Strict”关闭。只是一个想法。