OK, so this one is a bit odd.
I have a pretty straightforward DataTable
:
DataTable dt = new DataTable();
dt.Columns.Add("Display", typeof(string));
dt.Columns.Add("Value", typeof(string));
dt.Rows.Add("Equals", "==");
dt.Rows.Add("Is Less Than", "<");
dt.Rows.Add("Is Less Than Or Equal To", "<=");
dt.Rows.Add("Is Greater Than", ">");
dt.Rows.Add("Is Greater Than Or Equal To", ">=");
Now, if I attach this DataTable
to a brand new ComboBox
, everything works as it should.
comboBox1.DataSource = dt;
comboBox1.ValueMember = "Value";
comboBox1.DisplayMember = "Display";
This control displays Equals
, Is Less Than
, etc.
In my form, however, I have another ComboBox
that is dynamically created, given a dynamic name based on which row in a TableLayoutPanel
it is in, etc.
When I use the exact same code for it, it displays System.Data.DataRowView
for each dropdown selection:
So, rather than me going through each line of code one by one and trying to reverse engineer this, can someone tell me what would cause this to happen? Is there some predictable and repeatable way to cause this behaviour? I've tried all of the following to reverse it but nothing works:
dtecComboAction.DataSource = null;
dtecComboAction.DataBindings.Clear();
dtecComboAction.Items.Clear();
dtecComboAction.BindingContext = this.BindingContext;
I'm sure I'm doing something obvious somewhere else in my code but I can't figure it out.
Thanks!
The unexpected behavior described in the question is determined by a custom drawing of the OwnerDrawn ComboBox (a Custom Control derived from ComboBox).
When the DataSource of a ListControl is a complex object - as in this case, a DataTable.DefaultView (a DataView) - each Item in the list is a complex object itself, a DataRowView
here.
For this reason, when drawing the Items of an OwnerDrawn ListControl (overriding the OnDrawItem
method), the Item's text, as displayed in the Control's visible area, should be fetched using the ListControl's GetItemText() method.
This method fetches an Item's text (using the internal DataManager if a DataSource has been defined, or the TypeDescriptor if not), no matter what the Item's Type is:
If the DisplayMember property is not specified, the value returned by GetItemText(Object) is the value of the item's ToString method. Otherwise, the method returns the string value of the member specified in the DisplayMember property for the object specified in the item parameter.
.Net Source code of ListControl.GetItemText()
A classic mistake when drawing an Item's text it to convert to string the Item's object.
This can work if the Item's text is a simple string, not if it's a complex object. In this case [ComplexObject].ToString()
returns the object's data type (System.Data.DataRowView
, in this case).
protected override void OnDrawItem(DrawItemEventArgs e)
{
// (...)
e.DrawBackground();
using (var brush = new SolidBrush(ForeColor)) {
e.Graphics.DrawString(Items[e.Index].ToString()), Font, brush, e.Bounds);
}
// (...)
}
Simplified sample code, do not use :)
Using GetItemText()
, the property value of DisplayMember
is returned as a string. Since comboBox1.DisplayMember = "Display";
, it will return the content of the DataTable's Display
Column.
protected override void OnDrawItem(DrawItemEventArgs e)
{
// (...)
e.DrawBackground();
using (var brush = new SolidBrush(ForeColor)) {
e.Graphics.DrawString(GetItemText(Items[e.Index]), Font, brush, e.Bounds);
}
// (...)
}