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

DatagridviewCombobox Items collection cannot be modified when the DataSource property is set

我有一个使用 VB.NET 的 windows 表单应用程序,旨在升级 IIS Web 应用程序。

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

想法是,用户 select 更新了 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

这通常工作正常。我可以 select 来自 datagridview 和其他单元格中组合框的不同值,同时移动到不同的单元格。

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

这是错误:

错误消息: 设置数据源 属性 后无法修改项目集合。

错误堆栈跟踪: 在 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 事件期间没有弹出任何消息,一切似乎都在按预期进行。我可以继续 select 从组合框等输入值。但是,如果我再次改变焦点并返回到组合框,错误再次发生。

据我所知,是 ComboBox_Value_Changed 子例程中其他单元格的值变化导致了错误。删除这些会使错误消失。

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

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

首先,我强烈建议您在“编译”选项中打开“Option Strict”。您当前的代码不会在当前状态下编译。似乎有一些幕后演员在进行,这可能会带来问题。在任何情况下,最好打开“Option Strict”选项。

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

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

Dim comboBox As ComboBox

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

comboBox = CType(e.Control, ComboBox)

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

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 选项对您来说确实是一个好处。它会查找可能导致问题的事情,这些问题可能并不明显。

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

sender.Text

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

是什么意思,就是一个对象是automatically/behind场景获取CAST到不同的对象。具体来说 senderComboBox。在强类型语言中,这种转换必须显式完成。原因是这对您有帮助。编译器只是在说...... “嘿......你确定对象 (sender) 会像你期望的那样正确地转换为 ComboBox 吗?”

在这种特殊情况下,sender 是一个 Object… 而 Object 没有 Text 属性。因此,编译器会将“automatically/behind 场景”cast/narrow sender 对象转换为 ComboBox,因为那显然是 sender

诚然,这当然很方便,但是,这种情况并不少见 automatic/behind 对象的场景缩小到您可能意想不到的程度。因此来自编译器的危险信号。强类型语言不会自动为您执行此转换。从编码的角度来看……这是有道理的,我更愿意这样来确保我的类型正是我所期望的。

在所有情况下,当您遇到使用“严格”选项抛出的错误时,就像我们在这里遇到的那样,几乎总是可以通过显式执行此转换来轻松修复它们。在这种情况下,要摆脱延迟绑定问题,请将代码更改为...

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

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