vb.netwinformsdatagridviewcomboboxdatagridviewcomboboxcolumn

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


I have a windows forms application using VB.NET that is designed to upgrade IIS web applications.

The application has a datagridview that shows the list of web apps to upgrade. One of the columns is a DataGridViewComboBoxColumn. This combobox has its DataSource property set to a datatable. I do not use a bindingsource for the combobox.

The idea is that the user selects one of the values from the combobox in the datagridview row and several other cells’ values in the same datagridview row are updated.

The EditingControlShowing event handler is set to set the combobox’s SelectedIndexChanged event.

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

The ComboBox_Value_Changed is the subroutine that updates the other cells’ values.

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

Also have the datagridview’s CurrentCellDirtyStateChanged event handled to commit the current edit immediately as the datagridview’s EditMode property is set to EditOnEnter, as well as update a cell’s values in the current datagridview row.

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

This works fine generally. I can select different values from the comboboxes in the datagridview and the other cells update accordingly, all while moving to different cells.

The issue I’m experiencing is after the user changes the value for the combobox and then changes the focus on the form to something other than the datagridview and then clicks back onto one of the comboboxes. This is when the datagridview’s DataError event is triggered and I get handful of the same errors.

This is the error:

Error Message: Items collection cannot be modified when the DataSource property is set.

Error StackTrace: at System.Windows.Forms.ComboBox.CheckNoDataSource() at System.Windows.Forms.DataGridViewComboBoxCell.InitializeEditingControl(Int32 rowIndex, Object initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle) at System.Windows.Forms.DataGridView.InitializeEditingControlValue(DataGridViewCellStyle& dataGridViewCellStyle, DataGridViewCell dataGridViewCell)

The error obviously implies that the combobox’s items collection is getting modified when the datasource property has been set. However, nowhere in the code is the comboboxes’ items collection getting added, removed, or cleared.

If I continue through all the errors, or just not have any messages pop up during the datagrid's DataError event, everything seems to happen as it should. I can continue selecting values from the comboboxes, etc. But, if I change the focus again and come back to the combobox, the errors occur again.

From what I can tell, it’s the value changes of the other cells during the ComboBox_Value_Changed subroutine that is causing the error. Removing those makes the errors go away.

Now, this error doesn’t happen either if I use the SelectionChangeCommitted event instead of the SelectedIndexChanged event for the combobox to call the ComboBox_Value_Changed subroutine. This was the event I was initially using but it wasn't being consistently raised with certain user interactions on the form. Which is why I switched to the SelectedIndexChanged event after some research into those behaviors.

I'm not sure why the combobox's items collection is being modified during these circumstances. As stated before, I can just handle the datagridview's DataError event to not generate any messages and everything seems to work as expected. I would just like to know what's going here if at all possible. I don't like to suppress errors when they occur if I don't have to.


Solution

  • To start, I highly recommend you turn “Option Strict” ON in the “Compile” options. Your current code will not compile in its current state. It appears there is some behind the scenes casting going on and that may present problems. In any case, it is good practice to have the “Option Strict” option ON.

    I am confident your problem lies in the fact that the SelectedIndexChanged event subscribed to by the ComboBox created in the EditingControlShowing event is NEVER UN-Subscribed. This is going to cause the ComboBox_SelectedIndexChanged event to fire when it shouldn’t. Without unsubscribing, I am confident that the event is getting fired many more times that we need or in this case want. So you need to un-subscribe from the combo box event when the user “leaves” the combo box cell.

    Fortunately a simple solution is available. First, we need “global” access to the ComboBox created in the EditingControlShowing event comboBox. So, make it a “global” ComboBox variable…

    Dim comboBox As ComboBox
    

    Then change the comboBox code assignment in the EditingControlShowing event to something like…

    comboBox = CType(e.Control, ComboBox)
    

    Now that we have “global” access to comboBox, we can UN-Subscribe from its ComboBox_SelecedIndecChanged event in the grids CellLeave event. This event may look something like…

    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
    

    I am confident this will solve your issue. If you need more details let me know. Good Luck.

    Edit Per OP Comment

    Turning the Strict option on is really a benefit for YOU. It looks for things that may cause problems that may not be obvious.

    Example, in your current code, if you turn “Strict” on, it will flag the line…

    sender.Text
    

    in the ComboBox_SelectedIndexChanged event as a problem. It will display an error message with something like… “with the Strict option ON Late binding is not allowed.”

    Is what this means, is that an object is automatically/behind the scenes getting CAST to a different object. Specifically sender to a ComboBox. In a strongly typed language, this casting must be explicitly done. Reason being that this helps YOU. The compiler is simply saying… “hey… are you SURE that object (sender) will cast properly to a ComboBox as you expect?”

    In this particular case, sender is an Object… and an Object does NOT have a Text property. Therefore, the compiler is going to “automatically/behind the scenes” cast/narrow the sender object to a ComboBox since that is what sender obviously is.

    Granted, this is certainly convenient, however, it is NOT uncommon for this automatic/behind the scenes narrowing of the object to get narrowed to something you may not be expecting. Hence the red flag from the compiler. A strongly typed language will NOT automatically do this cast for you. And from a coding perspective… this makes sense and I would prefer this to make sure my types are exactly what I expect them to be.

    In all cases, when you get an error that is thrown from using the “Strict” option, as we have here, they can almost always be easily fixed by doing this casting explicitly. In this case, to get rid of the late binding issue, change the code to…

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

    … this change will remove the error. My point as we started, is that this helps YOU in avoiding possible errors. So it is good practice to adhere to what the “Strict” option ON compiler accepts. In addition, turning “Strict” ON and fixing the errors will allow the code to compile even if “Strict” is turned OFF. Just a thought.