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 sameDataSource
to which other controls are bound. - Set its
DisplayMember
to show in combo box, but you don't need to setValueMember
. - 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 toId
property ofproductsBindingSource
nameTextBox
→ Data Bindings:Name
property bound toName
property ofproductsBindingSource
priceTextBox
→ Data Bindings:Price
property bound toPrice
property ofproductsBindingSource
Then to have a ComboBox
to act as index, these are the settings for ComboBox
:
DataSource
property set toproductsBindingSource
DisplayMember
property set toName
- 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:
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:
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.
- Drop a
BindingSource
on your form; - Set your
ListBox.DataSource
property to yourBindingSource
; - Set your
ValueMember
andDisplayMember
properties just like you're actually doing; - Make your
DataBinding
for yourTextBox
control, and use yourBindingSource
as the source, using yourMyItem.Comment
property; - Assign your
List(Of MyItem)
to yourBinding.DataSource
property; - Your TextBox should follow the
CurrencyManager.CurrentItem
's Comment property, that is, the currentlyListBox.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 differentBindingList<T>
.combo1.DataSource = yourList.ToList();
combo2.DataSource = yourList.ToList();You can use different
BindingSource
for them and set your list asDataSource
of BindingSourcecombo1.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
C# 6.0 Features Not Working with Visual Studio 2015
Strip the Byte Order Mark from String in C#
Adding Parameters in SQLite with C#
Databinding to List - See Changes of Data Source in Listbox, Combobox
How to Reference Generic Classes and Methods in Xml Documentation
Read Connection String from Web.Config
How to Call .Net Methods from Excel Vba
Playing a Mp3 File in a Winform Application
Get All Controls of a Specific Type
Xml-Selectnodes with Default-Namespace via Xmlnamespacemanager Not Working as Expected
Does Anyone Still Use [Goto] in C# and If So Why
Returning Only Part of Match from Regular Expression