Dynamically add item to DataGridView ComboBox Colu

2020-04-23 04:13发布

问题:

I have a DataGridView that has a ComboBox column and I must update each ComboBox's possible values when its drop down shows. I also must make the ComboBoxes capable of having custom typed values. When a new value is typed, it should be added to the list of possible values. The problem is that I get infinitely many DataError event triggers (error message boxes), I know how to handle it by just changing a field in the DataGridViewDataErrorEventArgs object, but I know it is not the correct way to handle it:

private void DataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
{
    e.Cancel = false;
}

If I do it in the incorrect way, after selecting a value from the drop down or typing a new value, the CellValueChanged is triggered but the closed ComboBox does not display the current value but an already existing value (the first in the list).

In the following code the Form subclass is Form2, the initial values are stored in the str field and the UpdatePossibleValues method is called to update the possible values in all the ComboBoxes inside the only column in the data grid view, a DataGridViewComboBoxColumn:

public Form2()
{
    InitializeComponent();

    dataGridView1.EditingControlShowing += DataGridView1_EditingControlShowing;

    UpdatePossibleValues();
}

internal List<string> str = new List<string>()
{
    "val1",
    "val2"
};

private void DataGridView1_EditingControlShowing(object sender, 
    DataGridViewEditingControlShowingEventArgs e)
{
    if (dataGridView1.CurrentCell == null ||
        dataGridView1.CurrentCell.OwningColumn == null ||
        dataGridView1.CurrentCell.OwningColumn.Name != "column1")
    {
        return;
    }
    var combo = e.Control as DataGridViewComboBoxEditingControl;
    if (combo == null)
    {
        return;
    }

    var cb = combo as ComboBox;
    UpdatePossibleValues(cb);
    cb.DropDownStyle = ComboBoxStyle.DropDown; // this makes the ComboBoxes editable
    cb.Validating += Cb_Validating;
}

private void Cb_Validating(object sender, System.ComponentModel.CancelEventArgs e)
{
    var cbo = sender as ComboBox;
    string t = cbo.Text;

    var cell = (DataGridViewComboBoxCell)dataGridView1.CurrentCell;

    // add the value to the list if it is not there
    if (!string.IsNullOrEmpty(t) &&
        !cbo.Items.Contains(t))
    {
        str.Add(t);

        UpdatePossibleValues(cbo);

        cell.Value = t;

        e.Cancel = false;
    }
}

private void UpdatePossibleValues(ComboBox cb = null)
{
    if (cb == null)
    {
        var col = dataGridView1.Columns[0] as DataGridViewComboBoxColumn;

        col.Items.Clear();
        foreach (string s in str)
        {
            col.Items.Add(s);
        }
    }
    else
    {
        cb.Items.Clear();
        foreach (string s in str)
        {
            cb.Items.Add(s);
        }
    }
}

Screenshots:

回答1:

To dynamically add item to DataGridViewComboBoxColumn:

  1. Hanlde EditingControlShowing and get the DataGridViewComboBoxEditingControl
  2. Set editing control DropDownStyle to DropDown
  3. Handle Validating event of editing control and make sure you attach the event handler just once.
  4. Check if the Text of the editing control doesn't exists in the items:
    • Add it to data source of the column
    • Then reset data source of the column by setting it to null and assigning data source again.

Notes:

  • If you have multiple combo box, make sure you use different data sources for combo boxes and update corresponding data source in validating event.

  • If you handle the events using anonymous method, make sure you have a correct assumption about captured variables. To make it simple, you can handle the event using a normal method.

Example

The following example shows a DataGridView having two DataGridViewComboBoxColumn which for the second one, you can add new values by typing in the combo box at run-time.

To run the example, create a Form and drop a DataGridView on a new Form and just copy and paste the following code in the form:

private List<String> comboSource1;
private List<String> comboSource2;
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    comboSource1 = new List<string> { "A", "B" };
    comboSource2 = new List<string> { "1", "2" };

    var dt = new DataTable();
    dt.Columns.Add("C1");
    dt.Columns.Add("C2");
    dt.Rows.Add("A", "1");
    dt.Rows.Add("B", "2");

    var c1 = new DataGridViewComboBoxColumn();
    c1.Name = "C1";
    c1.DataPropertyName = "C1";
    c1.DataSource = comboSource1;

    var c2 = new DataGridViewComboBoxColumn();
    c2.Name = "C2";
    c2.DataPropertyName = "C2";
    c2.DataSource = comboSource2;

    dataGridView1.Columns.AddRange(c1, c2);

    this.dataGridView1.DataSource = dt;
    dataGridView1.EditingControlShowing += dataGridView1_EditingControlShowing;
    dataGridView1.EditMode = DataGridViewEditMode.EditOnEnter;
}
private void dataGridView1_EditingControlShowing(object sender,
    DataGridViewEditingControlShowingEventArgs e)
{
    var dataGridView = sender as DataGridView;
    if (dataGridView?.CurrentCell?.ColumnIndex != 1) return;
    var comboBox = e.Control as DataGridViewComboBoxEditingControl;

    if (comboBox == null) return;
    comboBox.DropDownStyle = ComboBoxStyle.DropDown;
    if (!true.Equals(comboBox.Tag))
    {
        comboBox.Tag = true;
        comboBox.Validating += (obj, args) =>
        {
            var column = (DataGridViewComboBoxColumn)dataGridView.CurrentCell.OwningColumn;
            var list = comboBox.DataSource as List<string>;
            if (list == null) return;
            var txt = comboBox.Text;
            if (!list.Contains(txt))
            {
                list.Add(txt);
                column.DataSource = null;
                column.DataSource = list;
            }
            dataGridView.CurrentCell.Value = txt;
            dataGridView.NotifyCurrentCellDirty(true);
        };
    }
}