Databinding to List - See Changes of Data Source in Listbox, Combobox

Databinding to List - See changes of data source in ListBox, ComboBox

In Windows Forms, in a scenario that you want to see changes of data source in the bound list control, like ComboBox, ListBox or DataGridView (complex two-way data binding), you should use a class that implements IBindingList interface as DataSource of data binding. The most suitable implementation is BindingList<T>. This way each add/remove in the underlying data source of your control will be visible in the control immediately.

Please keep in mind, using BindingList<T> allows the bound control see added or removed items, but to see also property changes immediately, T should implement INotifyPropertyChanged. This way your your controls will be notified of PropertyChanged and always show fresh data.

Note 1 - Does ObservableCollection solve two-way data binding Probelm?

In Windows Form, a common mistake is using ObservableCollection that will not work for this requirement since it doesn't implement IBindingList.

Note 2 - Does BindingSource solve two-way data binding problem?

If the underlying data source of BindingSource doesn't implement IBindingList<T> it doesn't solve two way data binding problem. You need to notify controls to reload data from binding source, so you can call ResetBindings method of BindingSource. This way, bound controls will reload data from data source and show the latest data:

this.bindingSource1.ResetBindings(false);

Note 3 - I should use List<T>. How can I solve the problem using List<T>?

If you must use List<T>, then you can reset the databinding of your list box when you need, for example after each change you should assign its DataSource to null and again to the list of data:

this.listBox1.DataSource = null;
this.listBox1.DataSource = list;

How to refresh DataSource of a ListBox

listbox1.DataSource property looks for value changes but by assigning the same list all the time the value won't really change.

You can use a BindingList<T>, instead of your List<T>, to automatically recognize new items added. Your ShowData() method must be called once at startup.

public partial class MyForm:Form
{
public MyForm(){
InitializeComponent();
ShowData();
}

BindingList<MyData> data = new BindingList<MyData>();

private void ShowData()
{
listBox1.DataSource = data;
listBox1.DisplayMember = "Name";
listBox1.ValueMember = "Id";
}

private void buttonAddData_Click(object sender, EventArgs e)
{
var selection = (MyData)comboBox1.SelectedItem;
data.Add(selection);
}
}

ComboBox or ListBox as index of Form - Bind other controls to SelectedItem

When you want to setup a list control like ComboBox or ListBox to act as an index for data:

  • Set its DataSource property to the same DataSource to which other controls are bound.
  • Set its DisplayMember to show in combo box, but you don't need to set ValueMember.
  • Don't touch (DataBindings). You don't need to set up data-binding.

Example

Assuming you have a productsBindingSource, these are settings for bound controls:

  • idTextBox → Data Bindings: Text property bound to Id property of productsBindingSource
  • nameTextBox → Data Bindings: Name property bound to Name property of productsBindingSource
  • priceTextBox → Data Bindings: Price property bound to Price property of productsBindingSource

Then to have a ComboBox to act as index, these are the settings for ComboBox:

  • DataSource property set to productsBindingSource
  • DisplayMember property set to Name
  • Don't touch (DataBindings). You don't need to set up data-binding.

Then as a result, when you select an item from ComboBox, bound controls will show selected item and binding navigator will move as well:

Sample Image

How to bind a List to a ComboBox?

As you are referring to a combobox, I'm assuming you don't want to use 2-way databinding (if so, look at using a BindingList)

public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country(string _name)
{
Cities = new List<City>();
Name = _name;
}
}



List<Country> countries = new List<Country> { new Country("UK"), 
new Country("Australia"),
new Country("France") };

var bindingSource1 = new BindingSource();
bindingSource1.DataSource = countries;

comboBox1.DataSource = bindingSource1.DataSource;

comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Name";

To find the country selected in the bound combobox, you would do something like: Country country = (Country)comboBox1.SelectedItem;.

If you want the ComboBox to dynamically update you'll need to make sure that the data structure that you have set as the DataSource implements IBindingList; one such structure is BindingList<T>.


Tip: make sure that you are binding the DisplayMember to a Property on the class and not a public field. If you class uses public string Name { get; set; } it will work but if it uses public string Name; it will not be able to access the value and instead will display the object type for each line in the combo box.

Binding a TextBox to a ListBox SelectedItem

As described in the comments, associating a BindingList (or a DataTable) with a BindingSource can have some interesting benefits.

All bound controls are updated automatically when one of the elements of the BindingList is modified or a new element is added to the list.

You can use the MovePrevious(), MoveNext(), MoveFirst(), MoveLast() methods to navigate the elements in the BindingList (other useful methods and events are available, see the Docs about the BindingSource functionality).

Here, a BindingList<T> (where T is the Member class shown below) is set as the DataSource of a BindingSource. Both are Fields of a Form class, this can be modified as needed.

The BindingSource is then used as the DataSource of a ListBox.

The Text property of two TextBox controls is then bound, using the BindingSource, to one of the properties of the Member class. This way, the Text property is set to the current Item of the BindingList. All controls are synchronized:

txtMemberName.DataBindings.Add(new Binding("Text", membersSource, 
"FirstName", false, DataSourceUpdateMode.OnPropertyChanged));
txtMemberLastName.DataBindings.Add(new Binding("Text", membersSource,
"LastName", false, DataSourceUpdateMode.OnPropertyChanged));

This is how it works, in practice:

BindingSource and BindingList

Note that the current Item of the ListBox is updated in real time when the Text of a TextBox is modified.

BindingList<Member> members = null;
BindingSource membersSource = null;

public partial class frmMembers : Form
{
public frmMembers() {
InitializeComponent();
InitializeDataBinding();
}

private void InitializeDataBinding()
{
members = new BindingList<Member>();
membersSource = new BindingSource(members, null);

lstBoxMembers.DataSource = membersSource;
txtMemberName.DataBindings.Add(new Binding("Text", membersSource,
"FirstName", false, DataSourceUpdateMode.OnPropertyChanged));
txtMemberLastName.DataBindings.Add(new Binding("Text", membersSource,
"LastName", false, DataSourceUpdateMode.OnPropertyChanged));
}

private void btnAddMember_Click(object sender, EventArgs e)
{
var frmNew = new frmNewMember();
if (frmNew.ShowDialog() == DialogResult.OK && frmNew.newMember != null) {
members.Add(frmNew.newMember);
}
}

private void btnMovePrevious_Click(object sender, EventArgs e)
{
if (membersSource.Position > 0) {
membersSource.MovePrevious();
}
else {
membersSource.MoveLast();
}
}

private void btnMoveNext_Click(object sender, EventArgs e)
{
if (membersSource.Position == membersSource.List.Count - 1) {
membersSource.MoveFirst();
}
else {
membersSource.MoveNext();
}
}
}

Sample New Member Form:

public partial class frmNewMember : Form
{
public Member newMember;

private void btnSave_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtMemberName.Text) ||
string.IsNullOrEmpty(txtMemberLastName.Text)) return;
newMember = new Member(txtMemberName.Text, txtMemberLastName.Text);
}
}

Sample Member class:

[Serializable()]
public class Member
{
public Member() { }
public Member(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString() => $"{this.FirstName} {this.LastName}";
}

Winforms, databinding, Listbox and textbox

One of the easiest way, I guess, would be to use a BindingSource, setting it as the ListBox.DataSource property to your BindingSource on design.

  1. Drop a BindingSource on your form;
  2. Set your ListBox.DataSource property to your BindingSource;
  3. Set your ValueMember and DisplayMember properties just like you're actually doing;
  4. Make your DataBinding for your TextBox control, and use your BindingSource as the source, using your MyItem.Comment property;
  5. Assign your List(Of MyItem) to your Binding.DataSource property;
  6. Your TextBox should follow the CurrencyManager.CurrentItem's Comment property, that is, the currently ListBox.SelectedItem.

Indeed, you would perhaps need to implement the INotifyPropertyChanged interface to make it work properly.

But if this all work perfect with the SelectValue, why don't you just use it?

Bind multiple ComboBox to a single List - Issue: When I select an item, all combo boxes change

Since you are binding all combo boxes to the same data source - a single list - they are using a single BindingManagerBase.

So when you choose an item from one of combo boxes, the current Position of the shared binding manager base changes and all combo boxes goes to that position of their shared data source.

To solve the problem you can bind them to different data source:

  • You can bind them to yourList.ToList() or any other list for example different BindingList<T>.

    combo1.DataSource = yourList.ToList();
    combo2.DataSource = yourList.ToList();
  • You can use different BindingSource for them and set your list as DataSource of BindingSource

    combo1.DataSource = new BindingSource { DataSource= yourList};
    combo2.DataSource = new BindingSource { DataSource= yourList};

Also as another option:

  • You can use different BindingContext for your combo boxes. This way even when you bind them to a single list, they are not sync anymore.

    combo1.BindingContext = new BindingContext();
    combo1.DataSource = yourList;
    combo2.BindingContext = new BindingContext();
    combo2.DataSource = yourList;

In fact all controls of the form use a shared BindingContext. When you bind 2 controls to a same data source, then they also use the same BindingManagerBase this way, when you for example move to next record, all controls move to next record an show value from bound property of next record. This is the same behavior that you are seeing from your combo boxes. Being sync for controls which are using the same BindingManagerBase is a desired behavior. Anyway sometimes we don't need such behavior. The post shares the reason and the solution.

C# - Update databound combo box automatically

Solution 1

You can use an implementation of IBindingList as DataSource to view changes of data source in the bound list control (complex two-way data binding). The most suitable implementation is System.ComponentModel.BindingList<T>.

Then when you add items to the binding list, or remove item from it you will see changes immediately in the control.

Solution 2

But as a more simple solution with less changes for you, you can reset the databinding of your cmbProduct this way when you need; for example after a change, call RefreshBindings();:

public void RefreshBindings()
{
var list = put your updated list here;

this.cmbProduct.DataSource = null;
this.cmbProduct.DataSource = list;
this.cmbProduct.DisplayMember = "set the display member here";
this.cmbProduct.ValueMember = "set the value member here";
}


Related Topics



Leave a reply



Submit