A bound DataGridView ComboBox Column doesn’t allow user input. You might come across a situation where you need to make it easier for the user to select an item from the ComboBox and at the same time allow them to just enter free text.
The trick is to make the ComboBox column without a DataSource and then you load the items by code. Also you have to handle a couple of stuff such as the EditingControlShowing event to convert the ComboBox style to be DropDown instead of DropDownList, and some other handling to commit the ComboBox change.
I will leave you with the code:
Private Sub MyForm_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
'Get ComboBox items
Me.ComboBoxTableAdapter.Fill(Me.MyDataSet.ComboBoxTable)
'Fill the ComboBox
Dim row As DataRow
For Each row In Me.MyDataSet.ComboBoxTable
'Generic list used as a trick to have the ComboBox Items added with Value
MyComboBox.Items.Add(New GenericListItem(Of String)(row.Item("ColName"), row.Item("ColValue")))
Next
Me.GridTableAdapter.Fill(Me.MyDataSet.GridTable)
'Adding Grid handler to detect change in run time
AddHandler MyDataGridView.CurrentCellDirtyStateChanged, AddressOf MyDataGridView_CurrentCellDirtyStateChanged
End Sub
'Change ComboBox style to DropDown
Private MyDataGridView_EditingControlShowing(ByVal sender As Object, ByVal e As MyDataGridViewEditingControlShowingEventArgs) Handles MyDataGridView.EditingControlShowing
If TypeOf e.Control Is ComboBox Then
Dim cb As ComboBox = TryCast(e.Control, ComboBox)
cb.DropDownStyle = ComboBoxStyle.DropDown
End If
'When an item is selected from the ComboBox update the cell value
If MyDataGridView.Columns(MyDataGridView.CurrentCell.ColumnIndex).Name = "MyComboBox" Then
Dim selectedComboBox As ComboBox = DirectCast(e.Control, ComboBox)
RemoveHandler selectedComboBox.SelectionChangeCommitted, AddressOf selectedComboBox_SelectionChangeCommitted
AddHandler selectedComboBox.SelectionChangeCommitted, AddressOf selectedComboBox_SelectionChangeCommitted
End If
End Sub
Private Sub selectedComboBox_SelectionChangeCommitted(ByVal sender As Object, ByVal e As EventArgs)
Try
Dim selectedCombobox As ComboBox = DirectCast(sender, ComboBox)
If selectedCombobox.SelectedItem IsNot Nothing Then
'Use the List we created earlier so we could retrieve the Value instead of the diplayed text
Dim oItem As GenericListItem(Of String) = CType(selectedCombobox.SelectedItem, GenericListItem(Of String))
Me.MyDataGridView.Item("ColumnName", MyDataGridView.CurrentCell.RowIndex).Value = oItem.Value()
End If
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
End Sub
Private Sub MyDataGridView_CurrentCellDirtyStateChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyDataGridView.CurrentCellDirtyStateChanged
'Commit the change which was in run time so the Value changed event happens and then allow one check box
If MyDataGridView.IsCurrentCellDirty Then
MyDataGridView.CommitEdit(MyDataGridViewDataErrorContexts.Commit)
End If
End Sub
Private Sub MyDataGridView_CellValidating(sender As Object, e As System.Windows.Forms.MyDataGridViewCellValidatingEventArgs) Handles MyDataGridView.CellValidating
'Allow user to enter new values for all MyDataGridViewComboBox controls in the MyDataGridView
If (TypeOf CType(sender, MyDataGridView).EditingControl Is MyDataGridViewComboBoxEditingControl) Then
Dim cmb As MyDataGridViewComboBoxEditingControl = CType(CType(sender, MyDataGridView).EditingControl, MyDataGridViewComboBoxEditingControl)
If Not cmb Is Nothing Then
Dim grid As MyDataGridView = cmb.EditingControlMyDataGridView
Dim value As Object = cmb.Text
'// Add value to list if not there
If cmb.Items.IndexOf(value) = -1 Then
'// Must add to both the current combobox as well as the template, to avoid duplicate entries...
cmb.Items.Add(value)
Dim cmbCol As MyDataGridViewComboBoxColumn = CType(grid.Columns(grid.CurrentCell.ColumnIndex), MyDataGridViewComboBoxColumn)
If Not cmbCol Is Nothing Then
cmbCol.Items.Add(value)
End If
End If
grid.CurrentCell.Value = value
End If
End If
''Following Works for a specific combobox ///////////
'If e.ColumnIndex = MyMyDataGridView.Columns("MyDataGridViewComboBoxColSize").Index Then 'CType(sender, MyDataGridView).CurrentCell.ColumnIndex
' 'Dim cmb As ComboBox = CType(e.Control, ComboBox)
' Dim cmb As MyDataGridViewComboBoxEditingControl = CType(CType(sender, MyDataGridView).EditingControl, MyDataGridViewComboBoxEditingControl)
' If Not cmb Is Nothing Then
' Dim grid As MyDataGridView = cmb.EditingControlMyDataGridView
' Dim value As Object = cmb.Text
' '// Add value to list if not there
' If cmb.Items.IndexOf(value) = -1 Then
' '// Must add to both the current combobox as well as the template, to avoid duplicate entries...
' cmb.Items.Add(value)
' Dim cmbCol As MyDataGridViewComboBoxColumn = CType(grid.Columns(grid.CurrentCell.ColumnIndex), MyDataGridViewComboBoxColumn)
' If Not cmbCol Is Nothing Then
' cmbCol.Items.Add(value)
' End If
' grid.CurrentCell.Value = value
' End If
' End If
'End If
End Sub
Public Class GenericListItem(Of T)
Private mText As String
Private mValue As T
Public Sub New(ByVal pText As String, ByVal pValue As T)
mText = pText
mValue = pValue
End Sub
Public ReadOnly Property Text() As String
Get
Return mText
End Get
End Property
Public ReadOnly Property Value() As T
Get
Return mValue
End Get
End Property
Public Overrides Function ToString() As String
Return mText
End Function
End Class
I hope this helps, and if you have any question let me know.
Happy coding 🙂