Search code examples
c#datagridviewdatagridviewcomboboxcell

A self-learning C# DataGridView Combobox?


I would like to have a column of ComboBoxes in a DataGridView, which allows the user to freely input some text, which is collected in the dropdown menus, so that entering the same text in the next box is faster. I'd prefer not to use DataGridViewComboBoxColumn, unless I really have to.

The following code nearly does the job but has these issues:

  • After entering some new text and hitting return, the newly entered text is immediately replaced with the old value

  • But the new text is successfully added to the dropdown menus of all of the comboboxes

  • when I select this newly added text in one of the boxes, I get DataGridView-Exceptions complaining about an invalid value.

It seems the boxes somehow have for validation purposes a copy of the datasource which doesn't get updated?

    public partial class Form1 : Form
    {
        List<string> data = new List<string>();     // shared data source for all ComboBoxes

        private void checkData(string s)            // check wether s in the list, add it if not, keep things sorted
        {
            if (data.Contains(s))
                return;
            data.Add(s);
            data.Sort();
        }

        private void addCell(string s)          // add a new cell to the grid
        {
            checkData(s);
            DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
            c.DataSource = data;
            c.Value = s;
            int i = theGrid.Rows.Add();
            theGrid.Rows[i].Cells[0] = c;
        }

        public Form1()
        {
            InitializeComponent();
            theGrid.ColumnCount = 1;
            addCell("Foo");
            addCell("Bar");
        }

        // handler to enable the user to enter free text
        private void theGrid_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            ComboBox cb = e.Control as ComboBox;
            if (cb != null)
                cb.DropDownStyle = ComboBoxStyle.DropDown;
        }

        // handler which adds the entered text to the data source
        private void theGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        { 
            if (e.RowIndex < 0 || e.ColumnIndex < 0)
                return;
            checkData(e.FormattedValue.ToString());
        }
    }

Solution

  • After some tests, I am guessing that the individual combo boxes are not getting updated as you think they are. It appears that in the checkData method, that the data is updated with a new s. This will visually update the combo box cell, however the DataSource for each of the combos needs to be updated. Hence the DataError exception when the new added value is selected.

    Considering that each combo box cell is “independent” and not part of a DataGridViewComboBoxColumn… then a loop through all the rows is necessary to update each combo box cell. I am not sure why a DataGridViewComboBoxColumn would not be used here.

    private void checkData(string s)            // check wether s in the list, add it if not, keep things sorted
    {
      if (data.Contains(s))
        return;
      data.Add(s);
      data.Sort();
      // now because each cell is independent... we have to update each data source! 
      UpdateCombos();
    }
    
    private void UpdateCombos() {
      foreach (DataGridViewRow row in theGrid.Rows) {
        if ((!row.IsNewRow) && (row.Cells[0].Value != null)) {
          string currentValue = row.Cells[0].Value.ToString();
          DataGridViewComboBoxCell c = new DataGridViewComboBoxCell();
          c.Value = currentValue;
          c.DataSource = data;
          row.Cells[0] = c;
        }
      }
    }
    

    Using the posted code, A call to UpdateCombos is added to the checkData method. This method as expected loops through all the rows in the grid and replaces each combo box with the updated data. I will not disagree that it may be prudent to replace the data source, however I would use a combo box column, which the code below does. With this change, the UpdateCombos is not needed and simply update the combo box column.

    The DataGridViewComboBoxColumn is exposed since the data source is updated frequently.

    private List<string> comboData;
    private DataGridViewComboBoxColumn comboColumn;
    
    private void Form2_Load(object sender, EventArgs e) {
      comboData = new List<string>();
      comboData.Add("Foo");
      comboData.Add("Bar");
      comboColumn = new DataGridViewComboBoxColumn();
      comboColumn.DataSource = comboData;
      theGrid2.Columns.Add(comboColumn);
      theGrid2.RowCount = 3;
    }
    
    private void checkData2(string s) { 
      if (!comboData.Contains(s)) {
        comboData.Add(s);
        comboData.Sort();
        comboColumn.DataSource = null;
        comboColumn.DataSource = comboData;
      }
    }
    

    Hope that helps