How to update combo box using data binding in winforms - c#

Hello I am looking at data binding in a winform application.
I have two classes: employee and job defined as below.
public class Employee
{
public string Name { set; get; }
public int Id { set; get; }
public Employee(string name, int id)
{
Name = name;
Id = id;
}
}
public class Job
{
public string Name { set; get; }
public Employee Employee { set; get; }
public Job(string name, Employee employee)
{
Name = name;
Employee = employee;
}
}
On one panel, I have one listbox (listBoxEmployees) that lists all the employees, and two textboxes giving id and name of the current employee. I have also one listbox (listBoxJobs) that lists all the jobs, one textbox giving the name of the current job as well a combobox used to select an employee for the current job. I would like that the item listed in the comboxbox get populated from the list of employees.
Any Idea how to do that? Thanks a lot for your help.
The code is as follows.
public Form1()
{
InitializeComponent();
var myListOfEmployees = new BindingList<Employee>
{
new Employee("Employee1", 1),
new Employee("Employee2", 2),
new Employee("Employee3", 3),
};
var bindingSourceEmployees = new BindingSource {DataSource = myListOfEmployees};
var myListOfJobs = new BindingList<Job>
{
new Job("Job1",null),
new Job("Job2", null),
};
var bindingSourceJobs = new BindingSource {DataSource = myListOfJobs};
//Controls Employees
listBoxEmployees.DataSource = bindingSourceEmployees;
listBoxEmployees.DisplayMember = "Name";
textBoxNameEmployee.DataBindings.Add("Text", bindingSourceEmployees,
"Name", true, DataSourceUpdateMode.OnPropertyChanged);
textBoxIdEmployee.DataBindings.Add("Text", bindingSourceEmployees,
"Id", true, DataSourceUpdateMode.OnPropertyChanged);
//Controls Jobs
listBoxJobs.DataSource = bindingSourceJobs;
listBoxJobs.DisplayMember = "Name";
comboBoxJob.DataSource = myListOfEmployees;
comboBoxJob.DataBindings.Add(new Binding("SelectedItem", bindingSourceJobs, "Employee"));
comboBoxJob.DisplayMember = "Name";
textBoxNameJob.DataBindings.Add("Text", bindingSourceJobs,
"Name", true, DataSourceUpdateMode.OnPropertyChanged);
}

Related

Data is not added well to ListView. (C# window form)

I added 3 columns.
The modified attribute values are as follows.
View - Details
Onwer - True
GirdLines - True
FullRowSelect - True
namespace TestWinForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void add_Click(object sender, EventArgs e)
{
//listView1.BeginUpdate();
string[] row = { "123", "456", "789" };
ListViewItem list_view = new ListViewItem(row);
listView1.Items.Add(list_view);
textBox1.Text = listView1.Items.Count.ToString();
//listView1.EndUpdate();
}
}
}
It is a code that updates the number of current rows to the textbox1 after adding a data row every time the add button is clicked.
Obviously, The number of rows keeps going up.... but the data is not output to listView1.
Which part should I check?
What you have should work in detail view with columns added. Here is an example
Design view
In this case a instance of the following class is used so later we can get information about a specific row.
public class Contact
{
public int CustomerIdentifier { get; set; }
public string CompanyName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneTypeDescription { get; set; }
public string PhoneNumber { get; set; }
public string CountryName { get; set; }
public string[] ItemArray => new[]
{
CompanyName,
FirstName,
LastName,
PhoneNumber,
CountryName
};
}
Code to do a mock add
private void AddRowButton_Click(object sender, EventArgs e)
{
var companyName = "Just added";
Contact contact = new Contact()
{
CompanyName = companyName,
FirstName = "Karen",
LastName = "Payne",
PhoneNumber = "123-333-4444",
CountryName = "USA"
};
ownerContactListView.Items.Add(
new ListViewItem(contact.ItemArray)
{
Tag = contact.CustomerIdentifier
});
CountLabel.Text = $#"Row count: {ownerContactListView.Items.Count}";
var index = ownerContactListView.Items.IndexOf(
ownerContactListView.FindItemWithText(companyName));
ownerContactListView.Items[index].Selected = true;
ownerContactListView.EnsureVisible(index);
ActiveControl = ownerContactListView;
}
After adding the row above (note I shorten the form so what you see is a partial view)
You can see the entire the initial load, see the following.
Havent used Winforms in ages, but I remember that using a binding source was more reliable
var bindingSource = new BindingSource();
bindingSource.DataSource = dataTable;
grid.DataSource = bindingSource;
//Add data to dataTable and then call
bindingSource.ResetBindings(false)

How can I have two editable and updatable DataGridViews bounded to a list and sub-list?

I have a List<Input> named listInput that contains property of type a List<class> named friends:
List<Input> listInput=new List<Input>();
BindingList<Input> blInput;
BindingSource bsInput;
public class Input
{
public string Name { get; set; }
public List<Friend> friends{ get; set; }
}
public class Friend
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
I have two DataGridView controls, named dgInputand dgFriends.
dgInput is bound to a BindingList like this:
blInput = new BindingList<Input>(listInput);
bsInput = new BindingSource(blInput, null);
dgInput.DataSource = bsInput;
dgInput is editable and updatable at run-time after user changes any cell on dgInput.
The question is: how can I bind dgFriends to sublist friends, so that it will automatically update when the dgInput curretn Row changes?
It's also important that dgFriens can be updated when an User changes any cell on dgFriends (the properties of listInput must preserve the changes).
Here's a simple example:
The first DataGridView uses the BindingSource you defined. The DataSource of this BindingSource is set to the List<Input> collection. This DGV can only show the Name property from the List<Input>, since you cannot present Rows containing both single values and collections of values.
The DataMember of the BindingSource must be empty (or null): if you set Name as DataMember, you'll get an array of Char as a result instead of a string.
A second BindingSource is created: its DataSource is set to the existing BindingSource. In this case, the DataMember is set explicitly to the Friends property, which represents a List<class> objects (a single object that represents a collection that will provide the data to the Rows of the second DGV).
This generates an active binding (a relationship) between the two BindingSource objects: when the first BindingSource.Current object changes, the second BindingSource will follow, showing its Current object (the List<Friend> linked to the current Name property).
All properties of the two classes are editable.
Note:
I've implemented the INotifyPropertyChanged interface in the Input class to notify changes of the Name property: it's not strictly required (or, it' not required at all) in this context, but you may want to have it there, you'll quite probably need it later.
List<Input> listInput = new List<Input>();
BindingSource bsInput = null;
public SomeForm()
{
InitializeComponent();
bsInput = new BindingSource(listInput, "");
var bsFriends = new BindingSource(bsInput, "Friends");
dataGridView1.DataSource = bsInput;
dataGridView2.DataSource = bsFriends;
}
public class Input : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string m_Name = string.Empty;
public string Name {
get => m_Name;
set { m_Name = value;
NotifyPropertyChanged(nameof(this.Name));
}
}
public List<Friend> Friends { get; set; } = new List<Friend>();
private void NotifyPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class Friend {
public string FirstName { get; set; }
public string LastName { get; set; }
}
This is how it works:
Sample object initialization, for testing:
listInput.AddRange(new[] {
new Input() {
Name = "Name One", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of One"},
new Friend () { FirstName = "Second", LastName = "Friend of One"},
new Friend () { FirstName = "Third", LastName = "Friend of One"},
}
},
new Input() {
Name = "Name Two", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of Two"},
new Friend () { FirstName = "Second", LastName = "Friend of Two"},
new Friend () { FirstName = "Third", LastName = "Friend of Two"},
}
},
new Input() {
Name = "Name Three", Friends = new List<Friend>() {
new Friend () { FirstName = "First", LastName = "Friend of Three"},
new Friend () { FirstName = "Second", LastName = "Friend of Three"},
new Friend () { FirstName = "Third", LastName = "Friend of Three"},
}
}
});

Add a default item to c# combobox at last position along with IEnumerable<class>

This is the format how I'm getting my list of users from Stored Procedure.
IEnumerable<User> users = new List<User>();
Now I'm trying to bind this list to my combo-box like:
private void BindDropdownList(IEnumerable<User> users)
{
selectuser_dropdown.Items.Insert(0, "+ New User"); // Want this to be at last position.
selectuser_dropdown.DataSource = users;
selectuser_dropdown.DisplayMember = "FullName";
selectuser_dropdown.ValueMember = "Id";
}
My class is:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Username { get; set; }
public bool IsAdmin { get; set; }
public string FullName {
get
{
return LastName + ", " + FirstName;
}
}
}
But the New user doesn't seem to appear in my dropdownlist. What is the issue here and can anybody please suggest a way to do it? Thanks in advance.
your code before added Item on dropdown then set datasource
When you make a datasource assignment, you crush the old data.
add users list a new Item before set datasource for
selectuser_dropdown
or
set selectuser_dropdown dataSource then add
dropdown new Item selectuser_dropdown.Items.Insert(0, "+ New User");
private void BindDropdownList(IEnumerable<User> users)
{
// users.ToList().Add(new User(){ .. , .. , .. });
selectuser_dropdown.DataSource = users;
selectuser_dropdown.DisplayMember = "FullName";
selectuser_dropdown.ValueMember = "Id";
// selectuser_dropdown.Items.Insert(0, "+ New User");
}
do not judge me strictly, i think it would be elegantly to use the decorator pattern here. you could wrap default user list into UserListDecorator, thereby add a new element at the beginning or at the end of the list
public class UserListDecorator : IEnumerable<User>
{
private IEnumerable<User> users;
public UserListDecorator(IEnumerable<User> users)
{
this.users = users;
}
public IEnumerator<User> GetEnumerator()
{
var innerList = users.ToList();
innerList.Add(new User() {LastName = "New User", Id = 0});
return innerList.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
you can get two lists, the basic one and another with a new user option.
IEnumerable<User> users = new List<User>()
{
new User() { LastName = "One" },
new User() { LastName = "Two" }
};
var withNewUser = new UserListDecorator(users);
selectuser_dropdown.DataSource = withNewUser;
after trying i found that you cannot add items to a ComboBox after binding it to a data source. To add items from a ComboBox with a bound data source, you have to do it through data source itself.You can use a Datatable and then add your new row to it,
private void BindDropdownList(IEnumerable<User> users)
{
DataTable dt = users.ToDataTable();
selectuser_dropdown.DataSource = dt ;
selectuser_dropdown.DisplayMember = "FullName";
selectuser_dropdown.ValueMember = "Id";
DataRow dr = dt.NewRow();
dr["FullName"] = "Select";
dr["Id"] = 0;
dt.Rows.InsertAt(dr, 0);
}

Why does my data binding not work if the control is created in a class

I'm trying to bind data returned from my database to a combo box which I have declared inside a class and will be returning that combo box to the calling form. The problem I am having is the data binding isn't working, I've verified data is being returned but when I assign the data to the data source property its like the binding never happened. Is what I'm attempting to do not possible?
Here is the code in my class which returns the combo box to the calling form:
public ComboBox LoadCategoryData()
{
ComboBox cbx = new ComboBox();
FindCategoryRequest request = new FindCategoryRequest();
Service service = ServiceFactory.CreateService();
FindCategoryResponse response = service.FindCategories(request);
CategoryView viewItem = new CategoryView();
viewItem.Name = "Select Category";
IList<CategoryView> categories = response.Categories.ToList();
categories.Insert(0, viewItem);
cbx.DataSource = categories;
cbx.DisplayMember = "Name";
cbx.ValueMember = "Id";
return cbx;
}
Created sample application from your code, it was working fine for me.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var test = LoadCategoryData();
this.Controls.Add(test);
}
public ComboBox LoadCategoryData()
{
ComboBox cbx = new ComboBox();
CategoryView viewItem = new CategoryView();
viewItem.Name = "Select Category";
IList<CategoryView> categories = new List<CategoryView>() { new CategoryView() { Name = "Item1", Id = 1 } };
categories.Insert(0, viewItem);
cbx.DataSource = categories;
cbx.DisplayMember = "Name";
cbx.ValueMember = "Id";
return cbx;
}
}
public class CategoryView
{
public string Name { get; set; }
public int Id { get; set; }
}

How to bind a List to a ComboBox?

I want to connect a BindingSource to a list of class objects and then objects value to a ComboBox.
Can anyone suggest how to do it?
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
is my class and I want to bind its name field to a BindingSource which could be then associated with 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.
For a backgrounder, there are 2 ways to use a ComboBox/ListBox
1) Add Country Objects to the Items property and retrieve a Country as Selecteditem. To use this you should override the ToString of Country.
2) Use DataBinding, set the DataSource to a IList (List<>) and use DisplayMember, ValueMember and SelectedValue
For 2) you will need a list of countries first
// not tested, schematic:
List<Country> countries = ...;
...; // fill
comboBox1.DataSource = countries;
comboBox1.DisplayMember="Name";
comboBox1.ValueMember="Cities";
And then in the SelectionChanged,
if (comboBox1.Selecteditem != null)
{
comboBox2.DataSource=comboBox1.SelectedValue;
}
public MainWindow(){
List<person> personList = new List<person>();
personList.Add(new person { name = "rob", age = 32 } );
personList.Add(new person { name = "annie", age = 24 } );
personList.Add(new person { name = "paul", age = 19 } );
comboBox1.DataSource = personList;
comboBox1.DisplayMember = "name";
comboBox1.SelectionChanged += new SelectionChangedEventHandler(comboBox1_SelectionChanged);
}
void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
person selectedPerson = comboBox1.SelectedItem as person;
messageBox.Show(selectedPerson.name, "caption goes here");
}
boom.
Try something like this:
yourControl.DataSource = countryInstance.Cities;
And if you are using WebForms you will need to add this line:
yourControl.DataBind();
If you are using a ToolStripComboBox there is no DataSource exposed (.NET 4.0):
List<string> someList = new List<string>();
someList.Add("value");
someList.Add("value");
someList.Add("value");
toolStripComboBox1.Items.AddRange(someList.ToArray());
public class Country
{
public string Name { get; set; }
public IList<City> Cities { get; set; }
public Country()
{
Cities = new List<City>();
}
}
public class City
{
public string Name { get; set; }
}
List<Country> Countries = new List<Country>
{
new Country
{
Name = "Germany",
Cities =
{
new City {Name = "Berlin"},
new City {Name = "Hamburg"}
}
},
new Country
{
Name = "England",
Cities =
{
new City {Name = "London"},
new City {Name = "Birmingham"}
}
}
};
bindingSource1.DataSource = Countries;
member_CountryComboBox.DataSource = bindingSource1.DataSource;
member_CountryComboBox.DisplayMember = "Name";
member_CountryComboBox.ValueMember = "Name";
This is the code I am using now.
As a small addition to this, I tried to incorporate something similar to this code, and was frustrated that adding/removing from the list was not reflected in the ComboBox. This is because the Add/Remove does not trigger the OnPropertyChange.
If you want to Add/Remove and have them reflected in the ComboBox, you will need to change List<> to ObservableCollection
List<Country> Countries
Should be replaced with
private ObservableCollection<Country> countries;
public ObservableCollection<Country> Countries
{
get { return countries; }
set
{
countries= value;
OnPropertyChanged("Countries");
}
}
Where OnPropertyChanged and ObservableCollection comes from
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
All of this is more eloquently expressed in a previous explanation here

Categories