Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Combobox losing selected index after bound ItemSource collection changes

I've searched Google and here for answers, my problem is somewhat related to the below question, but different enough to warrant a new question.

Combo-box loses selection after collection changes

Basically, I have a WPF combobox which is bound to a ObservableCollection class. This class has extra functionality to delay collection change notifications if I need to do a number of alterations to it, such as clearing and refilling it to get a fresh snapshot of the database.

My combobox binding has both DisplayMemberPath and SelectedValuePath set. The SelectedValuePath resolves to an integer property.

The issue is the same as the referenced question, when I refresh the values in the bound collection, the bound ComboBox looses its selection, and goes blank (SelectedIndex = -1).

I can confirm that the CollectionChanged event does not get fired until the collection has re-populated and has the items back inside it.

More puzzling is if I do the following:

        using (_collection.DelayNotifications())
        {
            var items = _collection.ToArray();
            _collection.Clear();
            _collection.AddRange(items);
        }

The combobox does not lose its selected value.

Which suggests that it breaks if the items in the collection get replaced with new items retrieved from the database - I could accept this if I wasn't using the SelectedValuePath binding, but because I am, and because the integer values are the same, surely what I am doing should work?

I'm using .NET 3.5 SP1

Anyone got any ideas?

Edit

From the comments below and Blam's answer. I do accept that those are the reasons why its doing it. But it doesn't really help me.

I am binding the SelectedValue property of the Combobox to an Integer property on my view model. If I was to bind the SelectedItem, I would need to bind to a property of that object type on my view model - but it's the integer property I'm actually after.

At the moment I've "fixed" (read minor hack) the issue by forcing a property changed event for the property 'SelectedValue' is bound to. This seems to make the Combobox recheck its internal list for an item which matches on the defined SelectedValuePath.

The WPF Combobox must 'know' it has a SelectedValuePath value set, therefore I don't think its too far of a leap to assume it would adjust its item matching logic. However this is going outside of the scope SO is intended for.

I realise I'm probably going to just accept this is just how WPF works, but after fighting with data-bound comboboxes in WinForms for a few years, I kinda hoped I wouldn't have to with WPF :) - though saying that WPF Comboboxes are far better than WinForm ones.

like image 761
Marlon Avatar asked Aug 14 '13 15:08

Marlon


1 Answers

This statement is wrong

I could accept this if I wasn't using the SelectedValuePath binding, but because I am

You are not binding to SelectedValuePath.
You are binding to a collection of Objects.
SelectedValuePath is just for reporting has nothing to do with comparing objects for equality. DisplayMemberPath is just for reporting has nothing to do with comparing objects for equality.

You confuse SelectValuePath with SelectedItem.
ComboBox does not use SelectedValuePath to determine if two objects are equal.

From the documentation of SelectedValuePath:

Gets or sets the path that is used to get the SelectedValue from the SelectedItem.

In the sample that confuses you you are loading the same objects back in.

I am going to assume SelectedValuePath is a property named ID

If you clear and recreate an object with a ID of 6 it is not equal to the cleared object with an ID of 6.

Try this. Create two objects (o1 and o2) with an ID of 6 and compare o1.Equals(o2).

If you want two objects with an ID of 6 to be equal then you need to override GetHashCode and Equals. In Equals return true if both have an ID of 6. And you can use ID as the GetHashCode.

String is a reference type that will fool you.
string s1 = "cat";
string s2 = "cat";
s1.Equals(s2) will return true as String Equals is overridden to compare value.

like image 51
paparazzo Avatar answered Oct 12 '22 23:10

paparazzo