You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Winform C#:绑定BindingList的DataGridView下拉框选择值异常问题

问题描述

Winform窗体通过可视化编辑器创建,DataGridView绑定到EF类的BindingList。其中某字段通过ComboBox选择关联表的值,行数据的加载、修改、新增、删除均正常,但存在以下异常:

  • 在ComboBoxDataGridViewEditingControl的SelectionChangeCommitted事件中修改同行其他单元格值时,其他单元格值会正确变更,但ComboBox的选择会还原为原始值。

推测问题根源:ComboBox的新选择值未持久化到底层BindingList,修改其他单元格时DataGridView重新加载了ComboBox的原始值。尝试在修改前提交编辑,但第一次操作仍会还原,第二次才生效。

最小可复现代码

Form代码

public partial class Form1 : Form
{
    BindingList<MyClass> blDatasource;
    List<MyProduct> ProductDefinitions;

    public Form1()
    {
        InitializeComponent();

        // Products datasource
        ProductDefinitions = new List<MyProduct>();
        ProductDefinitions.Add(new MyProduct(0, "CPU", 660.0));
        ProductDefinitions.Add(new MyProduct(1, "Monitor", 150.0));
        ProductDefinitions.Add(new MyProduct(2, "Mouse", 5.0));

        // MyClass datasource
        blDatasource = new BindingList<MyClass>();
        blDatasource.AllowEdit = true;
        blDatasource.AllowNew = true;
        blDatasource.AllowRemove = true;

        MyClass temp = new MyClass();
        temp.Id = 0;
        temp.ProductId = 0;
        temp.Price = 0;
        temp.Quantity = 1;
        temp.Total = 0;
        blDatasource.Add(temp);

        temp = new MyClass();
        temp.Id = 1;
        temp.ProductId = 1;
        temp.Price = 2;
        temp.Quantity = 5;
        temp.Total = 10;
        blDatasource.Add(temp);

        temp = new MyClass();
        temp.Id = 2;
        temp.ProductId = 2;
        temp.Price = 1;
        temp.Quantity = 3;
        temp.Total = 3;
        blDatasource.Add(temp);

        myClassBindingSource.DataSource = blDatasource;

        // Set up Combobox datasource
        DataTable tempProducts = new DataTable();
        tempProducts.Columns.Add("Key", typeof(int));
        tempProducts.Columns.Add("Value", typeof(string));
        int cMaxWidth = 0;

        for (int i = 0; i < ProductDefinitions.Count; i++)
        {
            DataRow r = tempProducts.NewRow();
            string cName = ProductDefinitions[i].Name;
            cMaxWidth = Math.Max(cMaxWidth, TextRenderer.MeasureText(cName, dataGridView1.Font).Width);
            r.ItemArray = new object[] { i, cName };
            tempProducts.Rows.Add(r);
        }

        // Set Comobox datasource
        productIdDataGridViewComboBoxColumn.DataSource = tempProducts;
        productIdDataGridViewComboBoxColumn.ValueMember = "Key";
        productIdDataGridViewComboBoxColumn.DisplayMember = "Value";
        productIdDataGridViewComboBoxColumn.Width = cMaxWidth + SystemInformation.VerticalScrollBarWidth + 10;
    }

    private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
    {
        if (e.Control.GetType() == typeof(DataGridViewComboBoxEditingControl))
            ((ComboBox)e.Control).SelectionChangeCommitted += new EventHandler(comboBoxCell_SelectedChanged);
    }

    private void comboBoxCell_SelectedChanged(object sender, EventArgs e)
    {
        if (!(sender is DataGridViewComboBoxEditingControl))
            return;

        DataGridViewRow r = dataGridView1.CurrentRow;
        if (r != null)
        {
            int value = int.Parse(((DataGridViewComboBoxEditingControl)sender).SelectedValue.ToString());
            MyProduct p = ProductDefinitions.Single(x => x.Id.Equals(value));

            r.Cells[2].Value = p.Price;
            r.Cells[4].Value = p.Price * (double)((Int32)r.Cells[3].Value);
        }
    }
}

public class MyClass
{
    public int Id { get; set; }
    public int ProductId { get; set; }
    public double Price { get; set; }
    public int Quantity { get; set; }
    public double Total { get; set; }
}

public class MyProduct
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public double Price { get; set; }

    public MyProduct(int id, string name, double price)
    {
        Id = id;
        Name = name;
        Price = price;
    }
}

设计器相关代码

// 
// dataGridView1
// 
dataGridView1.AutoGenerateColumns = false;
dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
dataGridView1.Columns.AddRange(new DataGridViewColumn[] { idDataGridViewTextBoxColumn, productIdDataGridViewComboBoxColumn, priceDataGridViewTextBoxColumn, quantityDataGridViewTextBoxColumn, totalDataGridViewTextBoxColumn });
dataGridView1.DataSource = myClassBindingSource;
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.Location = new Point(0, 0);
dataGridView1.Name = "dataGridView1";
dataGridView1.RowTemplate.Height = 25;
dataGridView1.Size = new Size(800, 450);
dataGridView1.TabIndex = 0;
dataGridView1.EditingControlShowing += dataGridView1_EditingControlShowing;
// 
// idDataGridViewTextBoxColumn
// 
idDataGridViewTextBoxColumn.DataPropertyName = "Id";
idDataGridViewTextBoxColumn.HeaderText = "Id";
idDataGridViewTextBoxColumn.Name = "idDataGridViewTextBoxColumn";
// 
// productIdDataGridViewComboBoxColumn
// 
productIdDataGridViewComboBoxColumn.DataPropertyName = "ProductId";
productIdDataGridViewComboBoxColumn.HeaderText = "ProductId";
productIdDataGridViewComboBoxColumn.Name = "productIdDataGridViewComboBoxColumn";
productIdDataGridViewComboBoxColumn.Resizable = DataGridViewTriState.True;
productIdDataGridViewComboBoxColumn.SortMode = DataGridViewColumnSortMode.Automatic;
// 
// priceDataGridViewTextBoxColumn
// 
priceDataGridViewTextBoxColumn.DataPropertyName = "Price";
priceDataGridViewTextBoxColumn.HeaderText = "Price";
priceDataGridViewTextBoxColumn.Name = "priceDataGridViewTextBoxColumn";
// 
// quantityDataGridViewTextBoxColumn
// 
quantityDataGridViewTextBoxColumn.DataPropertyName = "Quantity";
quantityDataGridViewTextBoxColumn.HeaderText = "Quantity";
quantityDataGridViewTextBoxColumn.Name = "quantityDataGridViewTextBoxColumn";
// 
// totalDataGridViewTextBoxColumn
// 
totalDataGridViewTextBoxColumn.DataPropertyName = "Total";
totalDataGridViewTextBoxColumn.HeaderText = "Total";
totalDataGridViewTextBoxColumn.Name = "totalDataGridViewTextBoxColumn";
// 
// myClassBindingSource
// 
myClassBindingSource.DataSource = typeof(MyClass);

更新尝试

曾尝试通过BindingSource.RaiseListChangedEvents属性,在CellBeginEdit设为false、CellEndEdit设为true,虽能生效但需ComboBox失去焦点才同步,体验不佳。

解决方案

问题核心是SelectionChangeCommitted触发时,ComboBox的编辑状态未提交,修改其他单元格引发DataGridView刷新,导致ComboBox从原始数据源读取值。优化方案如下:

  1. 先提交ComboBox单元格的编辑,确保新选择的ProductId写入底层BindingList
  2. 直接操作绑定的数据源实体,而非修改DataGridView的Cell.Value,避免刷新冲突

修改后的comboBoxCell_SelectedChanged方法代码:

private void comboBoxCell_SelectedChanged(object sender, EventArgs e)
{
    if (!(sender is DataGridViewComboBoxEditingControl comboBoxCtrl))
        return;

    // 提交当前单元格的编辑,确保ProductId写入数据源
    dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);

    // 获取当前绑定的实体对象
    if (!(myClassBindingSource.Current is MyClass currentItem))
        return;

    int selectedProductId = (int)comboBoxCtrl.SelectedValue;
    MyProduct selectedProduct = ProductDefinitions.Single(x => x.Id == selectedProductId);

    // 直接修改实体属性,BindingList会自动通知DataGridView更新
    currentItem.Price = selectedProduct.Price;
    currentItem.Total = selectedProduct.Price * currentItem.Quantity;
}

额外优化建议

  • 若MyClass实现INotifyPropertyChanged接口,数据变更的通知会更及时,避免潜在的刷新延迟
  • 移除EditingControlShowing事件中重复绑定的问题(每次进入编辑都会添加事件,可先移除旧事件再添加新的):
private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    if (e.Control is DataGridViewComboBoxEditingControl comboBoxCtrl)
    {
        // 先移除旧的事件绑定,避免重复触发
        comboBoxCtrl.SelectionChangeCommitted -= comboBoxCell_SelectedChanged;
        comboBoxCtrl.SelectionChangeCommitted += comboBoxCell_SelectedChanged;
    }
}

内容的提问来源于stack exchange,提问作者raululm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:14:51