Click here to Skip to main content
15,892,005 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
C#
void Bind(ListControl control, IEnumerable<Entity> entities, string displayMember)
{
    control.DataSource = entities.ToArray();
    control.ValueMember = "ID";
    control.DisplayMember = displayMember;
}


I've got an Entity with an ID and various subclasses of this. The above method would perhaps live in a presenter (base) class.

When I run a form that calls this with "Name" as DisplayMember, something very odd happens: The list control (a ComboBox in my test, but these properties are inherited from ListControl) displays the entity ID rather than its Name.

I attached the debugger and stepped into the method. The parameter "displayMember" is correct ("Name") and no exceptions occur, but the DisplayMember property becomes "ID" when ValueMember is set (along with ValueMember itself) and then seemingly just ignores any calls to the setter, neither throwing an exception nor actually changing the property value.

If I don't invoke ToArray() and bind directly to the given enumerable it displays the name, but then I end up potentially binding two or more lists to the same data source, which causes other strange effects. For one thing it becomes impossible to select items independently in the lists linked to the same datasource. I've no idea why this is, but ToArray() overcomes this by making a shallow copy.

I've also tried to fill lists by directly modifying the Items collection:

C#
void Fill(ComboBox combo, IEnumerable<Entity> entities, string displayMember)
{
    combo.DataSource = null;
    combo.Items.Clear();
    combo.DisplayMember = displayMember;
    combo.ValueMember = "ID";
    foreach (var e in entities)
        combo.Items.Add(e);
}


Note that I can't use ListControl here, because it doesn't have the Items collection. Still, it's a possible workaround worth exploring, so I did.

This at first looked like it worked - it displays the name as it should. But again other, new problems soon crop up. In this case it's the SelectedValue property that just stops working, so I cannot do this:

C#
void Select(ListControl list, int entityID)
{
    list.SelectedValue = entityID;
}

void Select(ListControl list, Entity item)
{
    Select(list, item.ID);
}


I want to be able to use the same object instance that is loaded in the list itself, another instance representing the same entity, or just the ID value itself. In many cases the lists will contain cached data while the selected item will reflect a property value of some other entity. Say Transaction.CurrencyID or Transaction.Currency.

It's difficult to believe Microsoft's intention was to make it so that SelectedValue should only work when you have a DataSource, and DisplayMember should only work when you don't. Presumably it is this brand-new approach in the latest framework versions where exceptions are NOT thrown even when things fail that make it impossible for me to have any idea what I'm doing wrong...

How do I solve this?
Posted

Minor progress:

C#
void Bind(ListControl control, IEnumerable<entity> entities, string displayMember)
{
    control.DataSource = new BindingSource() { DataSource = entities };
    control.ValueMember = "ID";
    control.DisplayMember = displayMember;
}
</entity>


This seems to work, I can select items independently in the 2 combo boxes I've bound (to the same entity list) in my test app.

But if I take a shallow copy (using ToArray() or ToList()) it *still* doesn't work, exhibiting the "DisplayMember setter stops working" behavior again...
 
Share this answer
 
I'm afraid I don't quite understand this system. I want to add information, but not necessarily solutions, and I thought keeping a trail would be easier (to make as well as to follow) than editing the original post...

Latest: "Solution 1" produced yet another problem, even when I don't clone the collection. If I now set the Sorted property true the list suddenly displays the result of invoking ToString() on the entity rather than what I've chosen via DisplayMember. Again no exceptions, things just start behaving erratically.

(Presumably the sort becomes a problem because it sorts the underlying data source directly.. but frankly, if the control allows sorting, I *should* expect it to do some sort of shallow-cloning under the hood or otherwise implement sorting in such a way that the data source itself is NOT sorted, imho. And here I'm even using a BindingSource, which surely could have shallow-cloned the "real" data..!)

While this too can be partially worked around by overriding ToString, that really is not a good solution and allows only one way to view an entity in a list. I might want to display a currency as "NOK" or as "Norwegian Crowns" or as "NOK - Norwegian Crowns" and it would be acceptable for me to create an extra property that would return both code and description. But creating 3 Currency classes to cover the 3 cases is certainly NOT ok.

I'm getting ever closer to concluding simply that Windows Forms databinding simply doesn't work. And I'm not talking about complex binding where the underlying data changes (and the control needs to know so it can reflect the changes), or two-way binding where the control also updates the data source. Just filling a combo box and being able to sort it, select items, and have some quick & easy way to control exactly what strings end up in the lists is all I'm trying to do here - and yet it seems more than the framework can handle! Perhaps I really am doing something terribly wrong, but it's not like the doc says anything too loud and clear about the limitations. ListControl.DataSource just says the data source must implement either IList or IListSource.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900