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.
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.