I am trying to fill a ComboBox with multiple instances of a custom ComboBoxItem class. The ComboBoxItem class looks like this:
class ComboBoxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Text;
}
}
I can fill the CombBox and read it's values just fine. My only problem is when an existing item comes in, the values should be bound to my ComboBox. But I don't know how to tell the Binding that it should use ComboBoxItem.Value as Value field.
//what to put in place of "SelectedItem"??
comboBox.DataBindings.Add(new Binding("SelectedItem", row, "F_KundenId", true, DataSourceUpdateMode.OnPropertyChanged));
This is how I bind all my Windows ComboBoxes in my app:
First use this to load your dataSource, in this case a List:
public static void LoadComboBox(ComboBox comboBox, object dataSource, string valueMember, string displayMember)
{
comboBox.DataSource = dataSource;
comboBox.ValueMember = valueMember;
comboBox.DisplayMember = displayMember;
}
Then use this to bind the selected value to your "row" column "F_KundenId":
public static void BindComboBox(ComboBox comboBox, object boundDataSource, string boundDataMember)
{
comboBox.DataBindings.Clear();
comboBox.DataBindings.Add("SelectedValue", boundDataSource, boundDataMember);
}
And here is a helper method to do both in a single call:
public static void LoadAndBindComboBox(ComboBox comboBox, object dataSource, string valueMember, string displayMember,
object boundDataSource, string boundDataMember)
{
LoadComboBox(comboBox, dataSource, valueMember, displayMember);
BindComboBox(comboBox, boundDataSource, boundDataMember);
}
This code can be used with ANY datasources you want, and can bind to any column of a DataTable, DataRow or object.
Example:
LoadAndBindComboBox(comboBox, myItems, "Value", "Text", row, "F_KundenId");
Ok, alternatively to Mangist's answer, I have come up with a solution of my own. No need for any custom class. Just use Dictionary<Key, Value>. The only thing is that you have to be careful WHEN during runtime you set DisplayMember and ValueMember. If you reset the DataSource of the ComboBox (set it to null and rebind it), then you need to also set the two Member properties. Then just bind the Dictionary<Key, Value> to the ComboBox using a BindingSource object.
private void InitCombos()
{
Dictionary<string, int> items = GetItems();
combo.DisplayMember = "Key";
combo.ValueMember = "Value";
combo.DataSource = new BindingSource(items, null);
}
//This was where my problem was. I didn't set the two Member properties of my ComboBox,
//thus preventing correct rebinding of DataSource
public void combo2_SelectedIndexChanged(object sender, EventArgs e)
{
combo.DataSource = null;
Dictionary<string, int> newItems = GetItems();
combo.DisplayMember = "Key";
combo.ValueMember = "Value";
combo.DataSource = new BindingSource(items, null);
}
Related
When I set a DataSource on a control and want to use .ToString() as DisplayMember, I need to set the DisplayMember last or the ValueMember will override it.
MSDN on empty string as display member:
The controls that inherit from ListControl can display diverse types of objects. If the specified property does not exist on the object or the value of DisplayMember is an empty string (""), the results of the object's ToString method are displayed instead.
Code to reproduce:
Class:
class SomeClass
{
public string PartA { get; set; }
public string PartB { get; set; }
public string WrongPart { get { return "WRONG"; } }
public override string ToString()
{
return $"{PartA} - {PartB}";
}
}
Form:
var testObj = new SomeClass() { PartA = "A", PartB = "B" };
comboBox1.DataSource = new [] { testObj };
comboBox1.DisplayMember = "";
comboBox1.ValueMember = "WrongPart";
comboBox2.DataSource = new[] { testObj };
comboBox2.ValueMember = "WrongPart";
comboBox2.DisplayMember = "";
You can try it by making a new form and adding 2 combobox's.
Result:
Conclusion and question:
This can be easily fixed by setting them in the correct order however this is prone to errors, it also does not show this behavior if I use an actual property as DisplayMember instead of ""/ToString.
I would really like to know why it displays this behavior and if I could possibly set .ToString() explicitly as DisplayMember (for code clarity).
I have searched in the reference source and found this bit:
if (!newValueMember.Equals(valueMember)) {
// If the displayMember is set to the EmptyString, then recreate the dataConnection
//
if (DisplayMember.Length == 0)
SetDataConnection(DataSource, newValueMember, false);
SetDataConnection method signature:
private void SetDataConnection(object newDataSource, BindingMemberInfo newDisplayMember, bool force)
This sets a new DisplayMember
displayMember = newDisplayMember;
so now we've come to the root of the issue
I've written a simple example in which a DataGrid on a WPF form is populated entirely from the codebehind. A DataGridTemplateColumn with a ComboBox has the ItemsSource set to a DummyClass that contains two properties for the DisplayMember and the SelectedValue.
A DataTable is populated with a single column with two rows. The ItemsSource for the DataGrid is set to the default view of the DataTable.
When the code runs, each ComboBox in the DataGrid displays correctly and has the proper options available in the dropdown but does not display the values from the DataTable.
What binding am I missing to connect the ComboBox SelectedValue to the values from the DataTable?
public partial class MainWindow : Window
{
public class DummyClass
{
public int SelectedValue { get; set; }
public string DisplayValue { get; set; }
}
public ObservableCollection<DummyClass> DummyClassCollection;
public MainWindow()
{
InitializeComponent();
DummyClassCollection = new ObservableCollection<DummyClass>();
DummyClassCollection.Add(new DummyClass() { DisplayValue = "Item1", SelectedValue = 0 });
DummyClassCollection.Add(new DummyClass() { DisplayValue = "Item2", SelectedValue = 1 });
DummyClassCollection.Add(new DummyClass() { DisplayValue = "Item3", SelectedValue = 2 });
DataGridTemplateColumn templateColumn = new DataGridTemplateColumn();
DataTemplate dataTemplate = new DataTemplate();
FrameworkElementFactory control = new FrameworkElementFactory(typeof(ComboBox));
control.SetValue(ComboBox.ItemsSourceProperty, DummyClassCollection);
control.SetValue(ComboBox.DisplayMemberPathProperty, "DisplayValue");
//
//Some binding to connect ComboBox Selectedvalue to DataTable values
//
dataTemplate.VisualTree = control;
templateColumn.CellTemplate = dataTemplate;
templateColumn.Header = "DummyColumn";
dgGrid.Columns.Add(templateColumn);
DataTable table = new DataTable();
table.Columns.Add("DummyColumn");
table.Rows.Add(1);
table.Rows.Add(2);
dgGrid.AutoGenerateColumns = false;
dgGrid.ItemsSource = table.DefaultView;
}
}
Here is all you need...
control.SetValue(ComboBox.DisplayMemberPathProperty, "DisplayValue");
control.SetValue(ComboBox.SelectedValuePathProperty, "SelectedValue");
control.SetValue(ComboBox.SelectedValueProperty, new Binding("DummyColumn"));
I have a listbox and a ListItem object which I am adding to the listbox so that I can retrieve a value from the selected item which is different then the displayed member.
class ListItem
{
public string DisplayMember;
public string ValueMember;
public ListItem(string n,string v){
DisplayMember = n;
ValueMember = v;
}
}
public CompareTimeFramesForm()
{
InitializeComponent();
listBox1.Items.Add(new ListItem("# of Bookings", null));
listBox1.Items.Add(new ListItem("People", "guaranteed_count"));
}
This is a winform FYI.
The problem I am having is the item shown in the actual listbox has the object rather then the string I would like to be displayed in the first argument of the ListItem constructor.
It looks like Bookings.Helpers.ListItem rather then "# of Bookings"
In the designer I changed the displayMember property to DisplayMember and its not working.
From MSDN:
When an object is being added to the ListBox, the control uses the text defined in the ToString method of the object unless a member name within the object is specified in the DisplayMember property.
ListBox will convert item to string calling ToString(). In your case you just need to change your ListItem class like this:
class ListItem
{
public string DisplayMember;
public string ValueMember;
public ListItem(string n,string v) {
DisplayMember = n;
ValueMember = v;
}
public override string ToString() {
return DisplayMember;
}
}
As alternative you can se the DisplayMember property (in designer or with code) to use your property (you called that property DisplayMember but its name is free because it must be specified and it doesn't use any convention):
listBox1.DisplayMember = "DisplayMember";
try this it should work for you!
class ListItem
{
public string DisplayMember;
public string ValueMember;
public ListItem(string n,string v){
DisplayMember = n;
ValueMember = v;
}
}
public CompareTimeFramesForm()
{
InitializeComponent();
listBox1.Items.Add(new ListItem("# of Bookings", null).DisplayMember);
listBox1.Items.Add(new ListItem("People", "guaranteed_count").DisplayMember);
}
This might look really simple, but I am not able to figure out how to do it. I am not an expert with Databinding in C#.
I have a list of Class objects(It is a nested Class) which looks something like this :
public class IntVector
{
private string customerid;
private string hash_id;
private string client_name;
private string mobile_no;
private string address;
//Table
private List<CustomerInfo> customerinfo;
}
I have a list of IntVector
private List<IntVector> UserData;
Now how to set CustomerInfo as the Datasource for a DatagridView Control which is member of the list UserData.
Thanks
First, you have to expose your customerinfo list somehow (it is now private, so you can't get it from outside your IntVector class).
If it was public:
BindingSource bs = new BindingSource();
int indexInUserDataList = 0;
bs.DataSource = UserData[indexInUserDataList].customerinfo;
datagridview.DataSource = bs;
Also, you may want to consider using BindingList instead of List if you want to modify your list programmatically and want those changes to be propagated to the control (here the difference is explained List<T> vs BindingList<T> Advantages/DisAdvantages)
What your CustomerInfo class looks like? I assume you want to bind columns of DataGridView to public properties of CustomerInfo class, for exsample:
class CustomerInfo
{
public int Id {get;set;}
public string Name {get;set;}
public string Address {get;set;}
private string somePrivateData;
}
Now if AutoGenerateColumns in your DataGridView is set to true, then 3 columns "Id", "Name" and "Address" will be automatically created in your DataGridView. "somePrivateData" will be ignored.
If you want to define collumns yourself, you can do it like this:
// make sure to do it before binding DataGridView control
datagridview.AutoGenerateColumns = false;
DataGridViewTextBoxColumn col1 = new DataGridViewTextBoxColumn();
col1.DataPropertyName = "Name";
col1.HeaderText = "Customer name";
col1.Name = "column_Name";
datagridview.Columns.Add(col1);
DataGridViewTextBoxColumn col2 = new DataGridViewTextBoxColumn();
col2.DataPropertyName = "Address";
col2.HeaderText = "Address";
col2.Name = "column_Address";
datagridview.Columns.Add(col2);
You need to set the private list of customers as public:
public class IntVector
{
private string customerid;
private string hash_id;
private string client_name;
private string mobile_no;
private string address;
//Table
public List<CustomerInfo> customerinfo;
}
private List<IntVector> UserData;
//Populate the UserData list here
And then you can set the datasource to DataGridView like:
DataGridView.DataSource = UserData[0].customerinfo;
I hope that helps...
I am experiencing some problems while working with ComboBox.
The display member for my combobox is not being populated by the overridden ToString method of class MAP.
Here is my code:
Form1.cs:
private void Form1_Load(object sender, EventArgs e) {
...
...
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
...
...
}
MAPList.cs:
public class MAPList {
public readonly List<MAP> All;
public MAPList() {
All = new List<MAP>();
var MapData = // Getting map data
foreach(MAP m in MapData) {
All.Add(new Map(m.Name, m.Code));
}
}
}
MAP.cs:
public class MAP {
public readonly string Name;
private string code;
public string Code { get { return code; } }
public RadioCode(string Name, string Code) {
this.Name = Name;
this.code = Code;
}
public override string ToString() {
return String.Format("{0}: {1}", Name, Code);
}
}
ToString will not be called if you set ValueMember. If you do not set ValueMember it will work as expected but then of course Code will not be used as the selected value of the ComboBox.
Alternatively, if you wish to use ValueMember you may also want to set DisplayMember. You can create a property on your MAP that is used for display, i.e.:
public class MAP
{
public readonly string Name;
private string code;
public string Code { get { return code; } }
public string Display { get { return ToString(); } }
public MAP(string Name, string Code)
{
this.Name = Name;
this.code = Code;
}
public override string ToString()
{
return String.Format("{0}: {1}", Name, Code);
}
}
In the form you can then set DisplayMember:
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
comboBox1.DisplayMember = "Display";
This is because you've set your ValueMember property to "Code", so the values in the combobox are not your Map objects but rather the strings corresponding to their Code properties.
If you remove this line:
comboBox1.ValueMember = "Code";
...it will work as you expect.
If you want the ComboBox to display its items according to your Map type's ToString method, then Jakob's answer is right on: create a property on your Map type that provides a string formatted exactly how you want it, and set the DisplayMember property of the ComboBox to the name of this property.
this could be because u r using ValueMember. use DisplayMember Property, add another property on the Map class in the get of this property return the formatted string.
I know this is an old post, but if someone wants to use ToString() without creating a property to just call ToString(), you'll have to explicitly set the DisplayMember value to an empty string like this:
Form1.cs:
private void Form1_Load(object sender, EventArgs e) {
...
...
MAPList MAP = new MAPList();
comboBox1.DataSource = MAP.All;
comboBox1.ValueMember = "Code";
comboBox1.DisplayMember = ""; // Explicitly set it to an empty String
...
...
}