根据绑定到 BindingList 的 DataGridView 中的 ComboBoxCell 选择更改值时的奇怪行为 - Winform C#

问题描述 投票:0回答:1

我有一个带有 DataGridView 的表单(使用可视化编辑器设置),绑定到 EF 类的 BindingList。其中一个字段具有与另一个表相关的值(可通过组合框选择)。一切工作正常,行已加载、修改、添加到 BindingList 以及从中删除。

当我尝试根据组合框中选择的值更改同一行中其他单元格的值时,问题出现了。我选择在 ComboBoxDataGridViewEditingControl 的 selectedItemChange 事件中执行此操作,此时奇怪的行为发生了。如果我修改另一个单元格的 ReadOnly 属性,它会起作用,但是当我尝试修改另一个单元格的值时,它会更改其他单元格的值,但会将 ComboBox 的选择恢复为原始值。

我认为问题可能与新选定的值未保留到底层 BindingList 相关,并且更改另一个单元格的值时,它会加载 ComboBox 的原始值。

到目前为止,我尝试过的是在更改其他单元格的值之前提交编辑,但它没有按预期工作(第一次恢复选择,第二次更改它,就好像在提交之前加载了原始值一样)更改并在第二次加载第一次尝试中修改的值)。

这是一个最小的工作示例:

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;
    }
}

以及设计器代码的InitializeComponent方法的相关部分:

// 
// 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.SuspendBinding() 和 BindingSource.ResumeBinding(),但是 Microsoft 的文档 告诉我们这不适用于复杂的绑定对象像 DataGridView (因为它不会暂停事件触发,并建议使用 BindingSource 的“RaiseListChangedEvents”属性。

我尝试了在 CellStartEdit 和 CellEndEdit 事件中将 BindingSource 的“RaiseListChangedEvents”属性分别设置为 false 和 true 的方法,它开始工作,但只有当组合框失去焦点时才会同步。

private void dataGridView1_CellBeginEdit(object sender, DataGridViewCellCancelEventArgs e)
{
    myClassBindingSource.RaiseListChangedEvents = false;
}

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    myClassBindingSource.RaiseListChangedEvents = true;
}

有没有更好的方法?

c# winforms datagridview combobox bindinglist
1个回答
0
投票

dataGridView1_EditingControlShowing
存在一些问题。这是一个问题,您每次显示事件时都先
+=
而没有先
-=
。但我建议放弃整个方法。 你不需要它。

您所说的“恢复”项的“线索”似乎是(当我运行您的代码时)在组合框中选择新值后,DGV 仍处于编辑模式。

这很容易补救。处理

CellValueChanged
事件,如果是组合框列,则提交编辑。

dataGridView1.CellValueChanged += (sender, e) =>
{
    if (dataGridView1.Columns[nameof(MyClass.ProductId)].Index == e.ColumnIndex)
    {
        dataGridView1.EndEdit();
    }
};


绑定参考

这是正确设置

DataGridView
并在您的
INotifyPropertyChanged
中实施
MyClass
的参考。

public partial class Form1 : Form
{
    Dictionary<ProductType, MyProduct> ProductDefinitions;
    private DataGridView dataGridView1;
    public Form1() => InitializeComponent();
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);
        ProductDefinitions = new Dictionary<ProductType, MyProduct>
        {
            { ProductType.CPU, new MyProduct{ Id = 0, Name = nameof(ProductType.CPU), Price = 660.0 }},
            { ProductType.Monitor, new MyProduct{ Id = 1, Name = nameof(ProductType.Monitor), Price = 150.0 }},
            { ProductType.Mouse, new MyProduct { Id = 2, Name = nameof(ProductType.Mouse), Price = 5.0 }},
        };
        blDatasource = new BindingList<MyClass>
        {
            new MyClass{ 
                Id = 0, 
                ProductId = ProductType.CPU,
                Price = ProductDefinitions[ProductType.CPU].Price,
                Quantity = 1 },
            new MyClass{ 
                Id = 1,
                ProductId = ProductType.Monitor,
                Price = ProductDefinitions[ProductType.Monitor].Price,
                Quantity = 1 },
            new MyClass{ 
                Id = 2, 
                ProductId = ProductType.Mouse,
                Price = ProductDefinitions[ProductType.Mouse].Price,
                Quantity = 1 },
        };
        dataGridView1.AutoGenerateColumns = true; // HIGHLY recommended
        dataGridView1.DataSource = blDatasource;
        DataGridViewColumn oldColumn = dataGridView1.Columns[nameof(MyClass.ProductId)];
        DataGridViewComboBoxColumn cbColumn = new DataGridViewComboBoxColumn
        {
            Name = oldColumn.Name,
            HeaderText = oldColumn.HeaderText,
        };
        int swapIndex = oldColumn.Index;
        dataGridView1.Columns.RemoveAt(swapIndex);
        dataGridView1.Columns.Insert(swapIndex, cbColumn);
        cbColumn.DataSource = Enum.GetValues(typeof(ProductType));
        cbColumn.DataPropertyName = nameof(MyClass.ProductId);

        dataGridView1.CellValueChanged += (sender, e) =>
        {
            if (dataGridView1.Columns[nameof(MyClass.ProductId)].Index == e.ColumnIndex)
            {
                dataGridView1.EndEdit();
            }
        };
        blDatasource.ListChanged += (sender, e) =>
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemChanged:
                    switch (e.PropertyDescriptor?.Name)
                    {
                        case nameof(MyClass.ProductId):
                            MyClass item = blDatasource[e.NewIndex];
                            var def = ProductDefinitions[item.ProductId];
                            item.Price = def.Price;
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        };
    }
    BindingList<MyClass> blDatasource;
}

我的班级

[DebuggerDisplay("{Id} {ProductId}")]
public class MyClass : INotifyPropertyChanged
{
    public int Id
    {
        get => _id;
        set
        {
            if (!Equals(_id, value))
            {
                _id = value;
                OnPropertyChanged();
            }
        }
    }
    int _id = 0;
    public ProductType ProductId
    {
        get => _productId;
        set
        {
            if (!Equals(_productId, value))
            {
                _productId = value;
                OnPropertyChanged();
            }
        }
    }
    ProductType _productId = default;

    public double Price
    {
        get => _price;
        internal set
        {
            if (!Equals(_price, value))
            {
                _price = value;
                OnPropertyChanged();
            }
        }
    }
    double _price = default;

    public int Quantity
    {
        get => _quantity;
        set
        {
            if (!Equals(_quantity, value))
            {
                _quantity = value;
                OnPropertyChanged();
            }
        }
    }
    int _quantity = default;

    public double Total
    {
        get => _total;
        internal set
        {
            if (!Equals(_total, value))
            {
                _total = value;
                OnPropertyChanged();
            }
        }
    }
    double _total = default;

    private void OnPropertyChanged([CallerMemberName]string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        Total = Price * Quantity;
    }

    public event PropertyChangedEventHandler? PropertyChanged;
}
© www.soinside.com 2019 - 2024. All rights reserved.