为什么BindingList更改时会清除ComboBox.SelectedValue DataBinding上下文

本文关键字:ComboBox SelectedValue DataBinding 上下文 清除 BindingList 为什么 | 更新日期: 2023-09-27 18:21:05

我在业务层中有一些逻辑根据输入限制ComboBox选项,所以我需要更改底层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;
        }
    }
}

更新:在组合框中添加事件处理程序似乎有效:

void _mComboBox_SelectedValueChanged(object sender, EventArgs e)
{
      var binding = (sender as Control).DataBindings["SelectedValue"];
      if (binding != null)
          binding.WriteValue();
      this.Text = string.Format("SelectedValue: {0}; WifeCount: {1}", _mComboBox.SelectedValue, _mEntity.WifeCount);
}

为什么BindingList更改时会清除ComboBox.SelectedValue DataBinding上下文

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

编辑:砰!仓促行事,没有把问题读透。问题是,添加数据绑定显然默认为单向绑定(仅初始绑定值)。您需要做的是在将数据绑定添加到组合框时指定DataSourceUpdateMode:

_mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount", false, DataSourceUpdateMode.OnPropertyChanged);

只是在其他一切保持不变的情况下进行了测试,结果奏效了。让我知道!

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

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

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

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