C# 为什么BindingList更改时ComboBox.SelectedValue数据绑定上下文会被清除?

C# 为什么BindingList更改时ComboBox.SelectedValue数据绑定上下文会被清除?,c#,winforms,data-binding,combobox,C#,Winforms,Data Binding,Combobox,我在业务层中有一些逻辑根据输入限制组合框选项,因此我需要更改底层BindingList中的值。但当列表发生更改时,双向绑定就变成了从UI到实体的单向绑定 _mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount"); 分配按钮单击处理程序中出现问题的完整代码: using System; using System.Collections; using System.Collections.Generic; using S

我在业务层中有一些逻辑根据输入限制组合框选项,因此我需要更改底层BindingList中的值。但当列表发生更改时,双向绑定就变成了从UI到实体的单向绑定

_mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount");
分配按钮单击处理程序中出现问题的完整代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace EnumDataBinding
{
    public partial class Form1 : Form
    {
        ComboBox _mComboBox = new ComboBox();
        Button _mCheckButton = new Button();
        Button _mAssignButton = new Button();

        BindingList<OptionValue> _mBindingList = new BindingList<OptionValue>();
        List<OptionValue> _mCacheList = new List<OptionValue>();

        Entity _mEntity = new Entity();

        public Form1()
        {
            InitializeComponent();

            // create a reset button
            _mCheckButton.Size = new Size(100, 30);
            _mCheckButton.Text = "Check";
            _mCheckButton.Location = new Point(100, 100);
            _mCheckButton.Click += new EventHandler(_mCheck_Click);

            // create assignment button
            _mAssignButton.Size = new Size(100, 30);
            _mAssignButton.Text = "Assign";
            _mAssignButton.Location = new Point(100, 135);
            _mAssignButton.Click += new EventHandler(_mAssignButton_Click);

            // create a combo box
            _mComboBox = new ComboBox();
            _mComboBox.Size = new System.Drawing.Size(300, 30);
            _mComboBox.Location = new Point(100, 200);

            this.Controls.AddRange(new Control[] {
                _mComboBox,
                _mCheckButton,
                _mAssignButton
            });

            // fill the bindinglist
            _mBindingList.Add(new OptionValue("One", 1M));
            _mBindingList.Add(new OptionValue("Two", 2M));
            _mBindingList.Add(new OptionValue("Three", 3M));

            _mCacheList.Add(new OptionValue("One", 1M));
            _mCacheList.Add(new OptionValue("Two", 2M));
            _mCacheList.Add(new OptionValue("Three", 3M));
        }

        void _mAssignButton_Click(object sender, EventArgs e)
        {
            // reset options
            _mBindingList.Clear();
            foreach (var o in _mCacheList)
                _mBindingList.Add(o);

            // EXPECTED: Update ComboBox.SelectedValue and ComboBox.Text
            // RESULT: Does not happen.
            _mEntity.WifeCount = 3M;

            this.Text = string.Format("SelectedValue: {0}; WifeCount: {1}", _mComboBox.SelectedValue, _mEntity.WifeCount);
        }

        private void PrepareComboBox(ComboBox combobox, BindingList<OptionValue> list)
        {
            combobox.DropDownStyle = ComboBoxStyle.DropDown;
            combobox.AutoCompleteSource = AutoCompleteSource.ListItems;
            combobox.AutoCompleteMode = AutoCompleteMode.Suggest;
            combobox.DataSource = new BindingSource() { DataSource = list };
            combobox.DisplayMember = "Display";
            combobox.ValueMember = "Value";
            combobox.Text = string.Empty;
            combobox.SelectedText = string.Empty;
        }

        protected override void OnLoad(EventArgs e)
        {
            // combo box datasource binding
            PrepareComboBox(_mComboBox, _mBindingList);

            // entity data binding
            _mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount", false);

            base.OnLoad(e);
        }

        void _mCheck_Click(object sender, EventArgs e)
        {
            this.Text = string.Format("SelectedValue: {0}; WifeCount: {1}", _mComboBox.SelectedValue, _mEntity.WifeCount);
        }
    }

    public class Entity : INotifyPropertyChanged
    {
        decimal _mWifeCount;

        public decimal WifeCount { get { return _mWifeCount; } set { _mWifeCount = value; OnPropertyChanged("WifeCount"); } }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class OptionValue
    {
        string _mDisplay;
        object _mValue;

        public string Display { get { return _mDisplay; } set { _mDisplay = value; } }

        public object Value { get { return _mValue; } set { _mValue = value; } }

        public OptionValue(string display, object value)
        {
            _mDisplay = display;
            _mValue = value;
        }
    }
}

我认为,为了实现双向绑定,需要为绑定列表中的元素实现INotifyPropertyChanged接口。原因是用作数据源的BindingList不知道任何元素何时发生了更改,除非元素传递了该信息。但是,它仍然可以传递与要添加和删除的项相关的事件(假设您将AllowRemove/AllowNew属性指定为true),因为该事件位于列表的范围内,而不是单个元素

编辑:呸!仓促行事,没有彻底阅读问题。问题是,添加数据绑定显然默认为单向绑定(仅限初始绑定值)。将数据绑定添加到组合框时,需要指定DataSourceUpdateMode:

_mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount", false, DataSourceUpdateMode.OnPropertyChanged);
只是在其他一切都保持不变的情况下进行了测试,结果成功了。让我知道


编辑:所以它不起作用(我没有清除列表),我找到了原因。这就是我注意到的。出于某种原因,只要底层数据源发生更改,实体的绑定上下文就会被清除。不完全确定原因,但我已经非常明确地发现这就是问题所在。我发现的方法是在实体的_mComboBox的绑定上下文中添加一个监视:
\u mComboBox.BindingContext[\u mEntity]
,并跟踪绑定计数。一旦一个新项目被添加到_mBindingList,它似乎会破坏ComboBox的内部数据绑定,最终会删除我们设置的Entity.WifeCount->ComboBox.SelectedValue绑定。尝试了各种方法,但我不完全确定为什么PropertyManager会在底层数据源发生更改时删除绑定。

现在我了解了您要做的事情,我认为这可能是一个可行的解决方案:

双向绑定很好,但清除combobox的数据源也会杀死数据绑定。如果要更改绑定列表,则可能应该在数据源更改时重新绑定:

protected override void OnLoad(EventArgs e)
{
    // combo box datasource binding
    PrepareComboBox(_mComboBox, _mBindingList);

    // entity data binding
    UpdateBindings();

    base.OnLoad(e);
}


public void UpdateBindings()
{
    _mComboBox.DataBindings.Clear();
    if (_mBindingList.Count != 0) _mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount");
}

void _mAssignButton_Click(object sender, EventArgs e)
{
    _mBindingList.Clear();          
    foreach (var o in _mCacheList) _mBindingList.Add(o);
    // UPDATE BINDINGS HERE - Only do this if changing the binding source
    UpdateBindings();

    _mEntity.WifeCount = 3M;

    this.Text = string.Format("SelectedValue: {0}; WifeCount: {1}", _mComboBox.SelectedValue, _mEntity.WifeCount);
}

我认为更紧迫的问题是WifeCount?lols。。我只是随机选择一个变量。应该是LifeCount,但我习惯于将所有东西都重命名为on。哪个方向不起作用?对我来说,它是在实体上设置值。i、 e.单击“分配”按钮。@AaronMcIver-wow。。不错的投票。但人们不应该只是来检查一下妻子的数量!我没有尝试过这个,但是为什么要在OptionValue而不是Entity中实现呢?很抱歉,我已经更新了OP完整代码列表,以更好地反映我实现INotifyPropertyChanged的实际代码。更新后的问题出现在assign button click处理程序中,该处理程序现在有BindingList.Clear()并重新填充值。糟糕的是,我没有彻底阅读这个问题(如果我这样做的话,我会立即抓住它)。没有意识到您有一个单独的实体,它将触发属性更改,并且OptionValue类仅用于表示目的。我已经删除了不必要的代码并添加了我的最终解决方案,希望它能为您工作。”所以它不起作用(我没有清除列表),我明白了为什么“是的,真的很奇怪,很令人沮丧!更新:刚刚发现一篇有趣的StackOverflow文章,实际上解释了为什么会发生这种情况(尽管问题是针对WPF的)。我感觉同样的问题也在发生,SetBinding被称为添加到基础集合的副作用,BindingContext不一定知道继续使用哪个。链接:当INotifyPropertyChanged如SPFiredrake所述实现时,从源到组合框的绑定也会更新。我的问题是修改索引列表项会破坏它。我希望能够修改列表,然后在实体上分配一个值。我用您添加的代码(_mCacheList等)测试了上面的方法,它可以工作……您试过了吗?它在哪里断裂?如何操作?使用_mBindingList.Clear()和.Add()运行程序>选择两个>单击检查>表单。文本显示2-2>然后单击分配>表单。文本显示1-3。所需输出应为3-3。如果Clear()和Add()被注释掉,我们将得到所需的3-3。显然,Clear()和Add()对绑定做了一些奇怪的事情。但这是什么呢?以及如何克服?添加到我的答案上面-您仍然需要连接Form1更新答案中的事件处理程序-这是经过测试的,并且可以工作。如果绑定源发生更改,则此处需要重新绑定。我同意将其链接到实体的propertyChanged事件是低效的,但在更改绑定源时必须手动完成。如果捕获到绑定到列表中没有反映状态的源(即:在编辑期间),则可能还可以将其链接到BindingList的ListChanged事件
protected override void OnLoad(EventArgs e)
{
    // combo box datasource binding
    PrepareComboBox(_mComboBox, _mBindingList);

    // entity data binding
    UpdateBindings();

    base.OnLoad(e);
}


public void UpdateBindings()
{
    _mComboBox.DataBindings.Clear();
    if (_mBindingList.Count != 0) _mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount");
}

void _mAssignButton_Click(object sender, EventArgs e)
{
    _mBindingList.Clear();          
    foreach (var o in _mCacheList) _mBindingList.Add(o);
    // UPDATE BINDINGS HERE - Only do this if changing the binding source
    UpdateBindings();

    _mEntity.WifeCount = 3M;

    this.Text = string.Format("SelectedValue: {0}; WifeCount: {1}", _mComboBox.SelectedValue, _mEntity.WifeCount);
}