What happen to DataGridViewComboBoxColumn when Dis

2019-07-19 04:05发布

问题:

When I have a DataGridViewComboBoxColumn filled with binded values, and if I set the DisplayMember property, I get the DataError event raised with a FormatException:

DataGridViewComboBoxCell value is not valid

If DisplayMember is not set, and so the view is showing the result of .ToString(), all work as expected.

Here is a complete example:

public partial class Form1 : Form
{
    public Form1() { InitializeComponent(); }

    private void Form1_Load(object sender, EventArgs e)
    {
        var categories = new[] { CustomerCategory.Cat1, CustomerCategory.Cat2, CustomerCategory.Cat3 };
        this.dataGridView1.AutoGenerateColumns = false;
        this.dataGridView1.DataError += new DataGridViewDataErrorEventHandler(dataGridView1_DataError);
        this.dataGridView1.CellParsing += new DataGridViewCellParsingEventHandler(dataGridView1_CellParsing);
        this.dataGridView1.Columns.Add(new DataGridViewComboBoxColumn()
        {
            DataSource = categories,
            HeaderText = "Category",
            DataPropertyName = "Category",
            DisplayMember = "Name" // if we omit this line, there is not DataError event raised
        });

        this.dataGridView1.DataSource = new[] 
        { 
              new Customer() { Category = CustomerCategory.Cat1 } 
            , new Customer() { Category = CustomerCategory.Cat2 } 
            , new Customer() { Category = CustomerCategory.Cat3 } 
        }.ToList();
    }

    void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e)
    {
        var value = this.dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;
        var type = value != null ? value.GetType() : null;
        string message = "Error"
            + Environment.NewLine + " - Column : " + e.ColumnIndex
            + Environment.NewLine + " - Line  : " + e.RowIndex
            + Environment.NewLine + " - Value : " + Convert.ToString(value) + " (" + type + ")"
            + Environment.NewLine + " - Exception : " + e.Exception.Message;
        Debug.Fail(message);
    }

    void dataGridView1_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
    {
        //http://stackoverflow.com/questions/631126/how-to-bound-a-datagridviewcomboboxcolumn-to-a-object
        if (this.dataGridView1.CurrentCell.OwningColumn is DataGridViewComboBoxColumn)
        {
            var editingControl = (DataGridViewComboBoxEditingControl)this.dataGridView1.EditingControl;
            e.Value = editingControl.SelectedItem;
            e.ParsingApplied = true;
        }
    }
}

The model:

public class CustomerCategory
{
    public static readonly CustomerCategory Cat1 = new CustomerCategory { Name = "Cat1" };
    public static readonly CustomerCategory Cat2 = new CustomerCategory { Name = "Cat2" };
    public static readonly CustomerCategory Cat3 = new CustomerCategory { Name = "Cat3" };

    public string Name { get; set; }
    public override string ToString() { return this.Name; }
}
public class Customer { public CustomerCategory Category { get; set; } }

How can I specify my own DisplayMember without having this annoying DataError event raised?
The problem appears only with DataGridViewComboBoxColumn, not with a regular ComboBox.

Edit : After a few tests, I can say that:

[DisplayMember + Not ValueMember] = FAIL
[Not DisplayMember + ValueMember] = FAIL
[DisplayMember + ValueMember] = WIN

So my question could be rephrased as : Is there any documentation that explain precisely what will work and what won't; and how the DisplayMember + ValueMember are linked together like it seems to be?

Re-Edit :

An interesting reference: Problems with the DataGridViewComboBoxColumn

However, the DataGridViewComboBoxColumn doesn't work like this, although it will display the ToString value if you don't set the DisplayMember, something internally goes wrong when it tries to look up the SelectedItem, you have to set DisplayMember to a public property of your class. Even worse, the default behaviour if you don't set the ValueMember property is to return the DisplayMember, there's no way of getting actual item itself. The only work around is to add a property to your class that returns itself and set that property to the ValueMember. Of course, if your item isn't something you are able to change (such as one of the framework classes) you'll have to cludge together a container object that holds a reference to your item.

Is somebody have any information about the something internally goes wrong part?

回答1:

Following logic may help you to resolve your issue.

I think problem is in the order of code line. Assigning datasource after assign display member property may causes the error.

Change this line;

this.dataGridView1.Columns.Add(new DataGridViewComboBoxColumn()
{
    DataSource = categories,
    HeaderText = "Category",
    DataPropertyName = "Category",
    DisplayMember = "Category" // if we omit this line, there is not DataError event raised
});

TO

DataGridViewComboBoxColumn col = new DataGridViewComboBoxColumn();
col.HeaderText = "Category";
col.DataSource = categories;
col.DisplayMember = "Category";
col.DataPropertyName = "Category";
this.dataGridView1.Columns.Add(col);

The datasource must be assigned before DisplayMember and ValueMember.