I have some logic in the business layer that limits the ComboBox options according to inputs, so I need to change the values in the underlying BindingList. But when the list gets changed, the two-way binding becomes one-way from UI to Entity only.
_mComboBox.DataBindings.Add("SelectedValue", _mEntity, "WifeCount");
Full code with problem in the assign button click handler:
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;
}
}
}
Update: Adding an event handler to the ComboBox seems to work:
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);
}
I believe that in order to have two-way binding, you need to implement the INotifyPropertyChanged interface for the element that is in the binding list. The reason is that the BindingList that’s being used as the datasource doesn’t know when any of the elements have changed unless the elements pass on that information. However, it can still pass on events relating to items being added and removed (assuming you specify AllowRemove/AllowNew properties to true) because that event is in the realm of the list, not the individual elements.
Edit: Bah! Jumped the gun and didn’t read the question/problem thoroughly. Here’s the issue, adding databindings apparently defaults to a one way binding (initial bind value ONLY). What you need to do is specify the DataSourceUpdateMode when adding the databinding to the combobox:
Just tested this out with everything else staying the same and it worked. Let me know!
Edit: So it isn’t working (I wasn’t clearing the list), and I figured out why. So this is what I’m noticing. For some reason, the binding context for the entity is getting cleared anytime the underlying datasource is getting changed. Not entirely sure why, but I have most definitely found that that’s the issue. The way I found out was adding a watch to the binding context of the _mComboBox for the entity:
_mComboBox.BindingContext[_mEntity]and keeping track of the Bindings count. As soon as a new item gets added to the _mBindingList, it seems to muck with the internal databindings of the ComboBox, which ultimately drops the binding for the Entity.WifeCount -> ComboBox.SelectedValue binding we setup. Tried various things, but I’m not entirely sure WHY the PropertyManager drops the binding when the underlying datasource changes.