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从原始数据源读取值。优化方案如下:
- 先提交ComboBox单元格的编辑,确保新选择的
ProductId写入底层BindingList - 直接操作绑定的数据源实体,而非修改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
相关产品推荐
相关产品推荐

