How to bind a Dictionary<Int32, CustomClass> to a dropdown - c#

I've seen lots of posts about binding a dictionary to a drop down when the value is a string.
What if the value is a class with a particular property of that class being the one thats displayed in the drop down?
Dictionary<Int32, MyClass>
// Value
class MyClass {
public String Yer="123";
public String Ner="321";
}
How do I display property Yer in my dropdown that's bound to that dictionary?

You need to use the DataTextField and DataValueField properties to the combo. Try this:
private void Page_Load(object sender, EventArgs e)
{
List<MyDummyObject> data = new List<MyDummyObject>()
{
new MyDummyObject() {ID = 1, RandomBoolValue = true, SomeRandomDescription = "First item" }
,new MyDummyObject() {ID=2, RandomBoolValue = false, SomeRandomDescription = "Second item" }
};
comboBox1.DataTextField = "SomeRandomDescription";
comboBox1.DataValueField = "ID";
comboBox1.DataSource = data;
comboBox1.DataBind();
}
private class MyDummyObject
{
public int ID { get; set; }
public string SomeRandomDescription { get; set; }
public bool RandomBoolValue { get; set; }
public override string ToString()
{
return "zzzzzz";
}
}
The overridden ToString on MyDummyObject is just to prove that it isn't being called (which is the default action if you don't specify DataTextField or DataValueField).

Related

Winforms ListBox with DataSource bound to dotted path of nested object crashes when intermediate object of dotted path is null

The ListBox's DataSource is bound to Detail.Tags.
When I select the first row, ListBox populates as expected.
When I select the second row, the expected (and desired) result is that the ListBox simply displays nothing, because ItemB's Detail property is purposely null for demonstration purposes, so ItemB's Detail.Tags doesn't exist.
Actual result is that program crashes to desktop with System.ArgumentException: 'Complex DataBinding accepts as a data source either an IList or an IListSource.'
Minimal reproducible example:
public partial class Form1 : Form
{
private readonly IList<Item> _items;
private BindingSource _bs = new BindingSource(){ DataSource = typeof(Item) };
public Form1()
{
InitializeComponent();
_items = GenerateSampleItems();
}
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = _items;
listBox1.DataBindings.Add(new Binding("DataSource", _bs, "Detail.Tags", false, DataSourceUpdateMode.Never));
}
private void DataGridView1_SelectionChanged(object sender, EventArgs e)
{
if (dataGridView1.SelectedRows.Count == 1)
{
_bs.DataSource = dataGridView1.SelectedRows[0].DataBoundItem;
}
else
{
_bs.DataSource = typeof(Item);
}
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA"
,Detail = new Detail()
{
Expiration = new DateTime(2024,1,1)
,Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag()
{
TagName = "FirstT"
,TagValue = "FirstV"
}
,new Tag()
{
TagName = "SecondT"
,TagValue = "SecondV"
}
})
}
}
,new Item()
{
Name = "ItemB"
// Detail purposely omitted
}
,new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
}
You can solve this problem by Creating a BindingSource for each model:
Main BindingSource where its DataSource property is set to a list of Item. This one is the DataGridView.DataSource.
Second BindingSource to navigate the Detail data members of the main BindingSource.
Third one to navigate and display the Tags data members of the detail's BindingSource. This one is the ListBox.DataSource.
public partial class Form1 : Form
{
private IList<Item> _items;
private BindingSource _bsItems, _bsDetail, _bsTags;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_items = GenerateSampleItems();
_bsItems = new BindingSource(_items, null);
_bsDetail = new BindingSource(_bsItems, "Detail");
_bsTags = new BindingSource(_bsDetail, "Tags");
dataGridView1.DataSource = _bsItems;
listBox1.DataSource = _bsTags;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
_bsDetail.Dispose();
_bsTags.Dispose();
}
private IList<Item> GenerateSampleItems()
{
return new List<Item>()
{
new Item()
{
Name = "ItemA",
Detail = new Detail
{
Expiration = new DateTime(2024,1,1),
Tags = new BindingList<Tag>(new List<Tag>()
{
new Tag
{
TagName = "FirstT",
TagValue = "FirstV"
},
new Tag
{
TagName = "SecondT",
TagValue = "SecondV"
}
})
}
},
new Item()
{
Name = "ItemB"
// Detail purposely omitted
},
new Item()
{
Name = "ItemC"
// Detail purposely omitted
}
};
}
}
// Elsewhere within the project's namespace
public class Item
{
public string Name { get; set; }
public Detail Detail { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"Name: {Name} - Detail: {Detail}";
}
}
public class Detail
{
public DateTime Expiration { get; set; }
public BindingList<Tag> Tags { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
var tags = $"[{string.Join(", ", Tags)}]";
return $"Expiration: {Expiration} - Tags: {tags}";
}
}
public class Tag
{
public string TagName { get; set; }
public string TagValue { get; set; }
// Optional: Change, remove as needed...
public override string ToString()
{
return $"{TagName}: {TagValue}";
}
}
That's all. No need to add DataBindings nor to handle the grid's SelectionChanged event as shown in your code snippet.
On the other hand, if you need to display the selected Item.Detail.Tags, then you need to flatten them in a list whenever the grid's selection changes and bind the result to the ListBox.
// +
using System.Linq;
public partial class Form1 : Form
{
private BindingSource _bsItems;
public Form1() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_bsItems = new BindingSource(GenerateSampleItems(), null);
dataGridView1.DataSource = _bsItems;
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_bsItems.Dispose();
}
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
listBox1.DataSource = dataGridView1.SelectedCells
.Cast<DataGridViewCell>()
.Select(cell => cell.OwningRow).Distinct()
.Where(row => (row.DataBoundItem as Item)?.Detail != null)
.SelectMany(row => (row.DataBoundItem as Item).Detail.Tags)
.ToList();
}
}

How can I draw the string over the item in CheckedBoxList control?

I am trying to draw or change the string of the item in the CheckedListBox control. So I have created the custom control which is derived from CheckedListBox.
public class CheckedListBoxAdv : CheckedListBox
{
public CheckedListBoxAdv()
:base()
{
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
//I want to change the text alone this place. But I cannot access the text part of the item.
}
}
Is there any way to change the text alone?
You don't need any drawing. You can create a class Item containing a Value and a Name property and then override ToString() method of class to return what you need to show in CheckedListBox:
public class Item
{
public int Value { get; set; }
public string Name { get; set; }
public override string ToString()
{
return this.Name;
}
}
This way you can fill CheckedListBox with items. It shows Name properties but you also have access to Value property:
private void Form1_Load(object sender, EventArgs e)
{
this.checkedListBox1.Items.Clear();
this.checkedListBox1.Items.Add(new Item() { Value = 1, Name = "One" });
this.checkedListBox1.Items.Add(new Item() { Value = 2, Name = "two" });
this.checkedListBox1.Items.Add(new Item() { Value = 3, Name = "three" });
//Change the Name of item at index 1 (2,"two")
((Item)this.checkedListBox1.Items[1]).Name = "Some Text";
//But the value is untouched
MessageBox.Show(((Item)this.checkedListBox1.Items[1]).Value.ToString());
}
try this.checkedListBoxName.Items : checkedListBoxName is name of your checkedListBox
Ex: this.checkedListBoxName.Items[0] = "abc";

Dynamic combobox list databinding in C# WinForms

Have read a lot this resource and really like it, but now i met problem which i can't neither resolve by myself nor find similar solution.
I'm using C# winforms and linqtosql.
In my userforms I use additional view-class to bind list for comboboxes to let users be able to get and use a name-list of objects while being forbidden to get a whole object itself. (This is not question whether it is a good practice, no matter.)
For example(this is not real code, just for look):
ORM classes:
public class Contract
{
public string ID { get; set; }
public string Name { get; set; }
public Contractor Contractor { get; set; }
public string ContractorID { get; set; }
}
public class Contractor
{
public string ID { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
This is additional view-class which is mapping for sqlserver view Contractor_List (SELECT c.ID, c.Name FROM Contractors c)
public class Contractor_List
{
public string ID { get; set; }
public string Name { get; set; }
}
UserForm:
public class ContractForm : Form
{
void Init()
{
TextBox nameBox = new TextBox();
ComboBox contractorBox = new ComboBox();
BindingSource contractSource = new BindingSource();
contractSource.DataSource = typeof (Contract);
nameBox.DataBindings.Add("Text", contractSource, "Name", false, DataSourceUpdateMode.OnValidation);
contractorBox.DataBindings.Add("SelectedValue", contractSource, "ContractorID", false, DataSourceUpdateMode.OnValidation);
BindingSource contractorListSource = new BindingSource();
contractorListSource.DataSource = typeof (Contractor_List);
contractorBox.DisplayMember = "Name";
contractorBox.ValueMember = "ID";
}
}
OK.
My idea is to load contractorBox.DataSource (it’s binding source) when contractorBox.SelectedValue is set.
I found that SelectedValue is not overridable, so I decided to inherit combobox and to create in it a new property called “ID” and do following stuff instead
In form:
contractorBox.DataBindings.Add("ID", contractSource, "ContractorID");
In Control (this is real code):
object _id;
bool _listInitialized;
public object ID
{
get
{
return _id;
}
set
{
if (!_listInitialized)
{
var bindingSource = DataSource as BindingSource;
if (bindingSource != null)
{
var t = (bindingSource.DataSource as Type);
var rst = … //Getting List
if (rst!=null)
{
bindingSource.DataSource = rst;
_listInitialized = true;
SelectedValueChanged += delegate {
if (SelectedValue != ID)
{
ID = SelectedValue;
}
};
}
}
}
else
{
_id = value;
if (SelectedValue != ID)
{
SelectedValue = value;
}
}
}
}
So, this code works fine. I can load form, Contract object and list of contractors and get right contractor name in combobox.
But. I have problem with backing Binding of “ID” property. When contractor in combobox is changed Contract object doesn’t update (neither ContractorID, no Contractor Itself) while ID, SelectedValue and SelectedItem of combobox change properly.
Why? What have I do to make this working.
Hah. So Simple Solution.
public new object SelectedValue
{
get
{
return base.SelectedValue;
}
set
{
if (!DesignMode)
{
if (!_listInitialized)
{
var bindingSource = DataSource as BindingSource;
if (bindingSource != null)
{
var t = (bindingSource.DataSource as Type);
var rst = ...///how you get your type list
if (rst != null)
{
bindingSource.DataSource = rst;
_listInitialized = true;
}
}
}
else
{
base.SelectedValue = value;
}
}
}
}
May be will be helpful for someone.

WPF DataGrid - Add Items if Button was clicked

This is my code:
But I will Add the values (see code) if someone clicked the Button "Einfügen".
But it doesn't work, It only change his values!
Thanks for all helpers!
private void Einfügen_Click(object sender, RoutedEventArgs e)
{
var itemsEnd = new List<Plan>();
itemsEnd.Add(new Plan(LinieZ, Convert.ToString(Kurs.SelectedItem), AbfZ, VonZ, NachZ, AnkZ, "---"));
Plan.ItemsSource = itemsEnd;
}
class Plan
{
public string Linie { get; set; }
public string Kurs { get; set; }
public string Abfahrt { get; set; }
public string Von { get; set; }
public string Nach { get; set; }
public string Ankunft { get; set; }
public string Pause { get; set; }
public Plan(string Linie, string Kurs, string Abfahrt, string Von, string Nach, string Ankunft, string Pause)
{
this.Linie = Linie;
this.Kurs = Kurs;
this.Abfahrt = Abfahrt;
this.Von = Von;
this.Nach = Nach;
this.Ankunft = Ankunft;
this.Pause = Pause;
}
}
The problem is that you are resetting the ItemsSource each time to a brand new List (of size 1). You are not appending to the List, but instead, you are creating a List that only has the new item, then setting that List to the DataGrid.
You can have a predefined list that you add to.
Something like:
private ObservableCollection<Plan> _items = new ObservableCollection<Plan>();
public Window()
{
InitializeComponent();
Plan.ItemsSource = _items;
}
private void Einfügen_Click(object sender, RoutedEventArgs e)
{
_items.Add(new Plan(LinieZ, Convert.ToString(Kurs.SelectedItem), AbfZ, VonZ, NachZ, AnkZ, "---"));
}
Though, I would suggest not going this route. Look into MVVM, DataBinding, and Commands. Ideally, you would want to create a ViewModel that contains an ObservableCollection that is bound to the DataGrid. Inside that ViewModel will be a command that will add items to that ObservableCollection.

Initializing an object property to the value of another object property in DataGridView

I'm attempting to bind two objects (List LedgerEntries and List BuyerSellers) to a single DataGridView. LedgerEntry contains a property for Buyer_Seller, and I would like the end-user to select a Buyer_Seller from a combobox (populated by the BuyerSellers generic collection) in the DataGridView and the LedgerEntries string BuyerSeller property be set to the Buyer_Seller string Name property.
At the moment I'm only using one BindingSource and I'm not defining my own columns; they are auto-generated based on the object being bound to the DGV. Where I'm a little lost is how to ensure the property in one object is initialized to the value of the combobox that's populated by another object. Thanks in advance for any help.
Found what I was looking for here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/62ddde6c-ed96-4696-a5d4-ef52e32ccbf7/binding-of-datagridviewcomboboxcolumn-when-using-object-binding
public partial class Form1 : Form
{
List<LedgerEntry> ledgerEntries = new List<LedgerEntry>();
List<Address> addresses = new List<Address>();
BindingSource entrySource = new BindingSource();
BindingSource adSource = new BindingSource();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
entrySource.DataSource = ledgerEntries;
adSource.DataSource = addresses;
DataGridViewComboBoxColumn adr = new DataGridViewComboBoxColumn();
adr.DataPropertyName = "Address";
adr.DataSource = adSource;
adr.DisplayMember = "OrganizationName";
adr.HeaderText = "Organization";
adr.ValueMember = "Ref";
ledger.Columns.Add(adr);
ledger.DataSource = entrySource;
addresses.Add(new Address("Test1", "1234", 5678));
addresses.Add(new Address("Test2", "2345", 9876));
}
private void button1_Click(object sender, EventArgs e)
{
foreach (LedgerEntry le in ledgerEntries)
MessageBox.Show(le.Address.OrganizationName + " // " + le.Description);
}
}
public class LedgerEntry
{
public string Description { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string OrganizationName { get; set; }
public string StreetAddress { get; set; }
public int ZipCode { get; set; }
public Address(string orgname, string addr, int zip)
{
OrganizationName = orgname;
StreetAddress = addr;
ZipCode = zip;
}
public Address Ref
{
get { return this; }
set { Ref = value; }
}
public override string ToString()
{
return this.OrganizationName;
}
}

Categories