Search code examples
c#winformsdatagridviewcombobox

DataGridView ComboBox column that will accept any text


I want a column of a DataGridView to use a ComboBoxStyle.DropDown style ComboBox, where the user can either select one of the entries in the drop-down, or type arbitrary text.

At the moment, I'm using the code from this answer and I can type freely into the text box part of the ComboBox, but if I type something that isn't in the drop-down list then it isn't committed back to the data source and the field reverts to the original selection. Furthermore, if I programmatically set the text to something not in the drop-down list I get a DataError event "DataGridViewComboBoxCell value is not valid."

I'm using data binding; the DataGridView itself is bound to a BindingList<T>.

Unlike this question I don't want the free text added to the drop-down list.

To be clear, the column data type is string and I don't want it validated against the drop-down list of the ComboBox (or anything else for that matter).

(Do I have to create my own custom DataGridViewColumn descendent as described in How to: Host Controls in Windows Forms DataGridView Cells?)


Solution

  • I found a simple, if verbose, answer. (But I'd still like to know if there is a way to do this with the standard DataGridViewComboBoxColumn type.)

    I followed the method in How to: Host Controls in Windows Forms DataGridView Cells. My full solution is too long to post here, but I can summarise the changes to make it use a ComboBox instead of the example's DateTimePicker control.

    1. Rename the three classes DropDownComboBoxColumn, DropDownComboBoxCell, and DropDownComboBoxEditingControl respectively.

    2. Replace DateTime everywhere with string.

    3. Add property public ComboBoxStyle DropDownStyle { get; set; } to DropDownComboBoxColumn to allow the calling code to set the drop-down style.

    4. Remove code from DropDownComboBoxCell constructor.

    5. Remove code from DropDownComboBoxEditingControl constructor.

    6. Make DropDownComboBoxEditingControl derive from ComboBox instead of DateTimePicker.

    7. Replace OnValueChanged with OnTextChanged to account for the different naming in ComboBox versus DateTimePicker.

    8. Make the EditingControlFormattedValue property get and set the inherited Text property (instead of Value) and there is no parsing needed.

    9. Make ApplyCellStyleToEditingControl set ForeColor and BackColor instead of CalendarForeColor and CalendarMonthBackground.

    10. Make EditingControlWantsInputKey also claim F4 so it can be used to open and close the drop-down.

    11. Add the following code to PrepareEditingControlForEdit:

      DropDownComboBoxColumn col = _dataGridView.Columns[_dataGridView.CurrentCell.ColumnIndex] as DropDownComboBoxColumn;
      if (col == null)
      {
        throw new InvalidCastException("Must be in a DropDownComboBoxColumn");
      }
      DropDownStyle = col.DropDownStyle;
      // (If you don't explicitly set the Text then the current value is
      // always replaced with one from the drop-down list when edit begins.)
      Text = _dataGridView.CurrentCell.Value as string;
      SelectAll();
      

    Handle the DataGridView's EditingControlShowing event as in OhBeWise's answer to a related question to set up the drop-down items and if desired the auto-completion mode:

    private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
    {
      ComboBox box = e.Control as ComboBox;
      if (box != null)
      {
        box.AutoCompleteSource = AutoCompleteSource.ListItems;
        box.AutoCompleteMode = AutoCompleteMode.SuggestAppend;
        box.DataSource = _dropDownItems;
      }
    }
    

    If you want the same drop-down items for all rows then you could always make this a property of DropDownComboBoxColumn like DropDownStyle and set it up in PrepareEditingControlForEdit to avoid having to handle EditingControlShowing.