Lets say we got a simple VM class
public class PersonViewModel : Observable
{
private Person m_Person= new Person("Mike", "Smith");
private readonly ObservableCollection<Person> m_AvailablePersons =
new ObservableCollection<Person>( new List<Person> {
new Person("Mike", "Smith"),
new Person("Jake", "Jackson"),
});
public ObservableCollection<Person> AvailablePersons
{
get { return m_AvailablePersons; }
}
public Person CurrentPerson
{
get { return m_Person; }
set
{
m_Person = value;
NotifyPropertyChanged("CurrentPerson");
}
}
}
It would be enough to successfully databind to a ComboBox for example like this:
<ComboBox ItemsSource="{Binding AvailablePersons}"
SelectedValue="{Binding Path=CurrentPerson, Mode=TwoWay}" />
Notice that Person has Equals
overloaded and when I set CurrentPerson value in ViewModel it causes combobox current item to display new value.
Now lets say I want to add sorting capabilities to my view using CollectionViewSource
<UserControl.Resources>
<CollectionViewSource x:Key="PersonsViewSource" Source="{Binding AvailablePersons}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Surname" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</UserControl.Resources>
Now combobox items source binding will look like this:
<ComboBox ItemsSource="{Binding Source={StaticResource PersonsViewSource}}"
SelectedValue="{Binding Path=CurrentPerson, Mode=TwoWay}" />
And it will be indeed sorted (if we add more items its clearly seen).
However when we change CurrentPerson
in VM now (before with clear binding without CollectionView it worked fine) this change isn't displayed in bound ComboBox.
I believe that after that in order to set CurrentItem from VM we have to somehow access the View (and we dont go to View from ViewModel in MVVM), and call MoveCurrentTo
method to force View display currentItem change.
So by adding additional view capabilities (sorting ) we lost TwoWay binding to existing viewModel which I think isn't expected behaviour.
Is there a way to preserve TwoWay binding here ? Or maybe I did smth wrong.
EDIT: actually situation is more complicated then it may appear, when I rewrite CurrentPerson setter like this:
set
{
if (m_AvailablePersons.Contains(value)) {
m_Person = m_AvailablePersons.Where(p => p.Equals(value)).First();
}
else throw new ArgumentOutOfRangeException("value");
NotifyPropertyChanged("CurrentPerson");
}
it works
fine
!
Its buggy behaviour, or is there an explanation? For some reasons even though Equals
is overloaded it requires reference equality of person object.
I really don't understand why It needs reference equality so I am adding a bounty for someone who can explain why normal setter doesn't work, when Equal
method is overloaded which can clearly be seen in "fixing" code that uses it