Вас связывают две проблемы, но вы выделили реальную проблему с использованием CollectionViewSource с ComboBox. Я все еще ищу альтернативы, чтобы исправить это «лучшим способом», но ваше исправление setter устраняет проблему по уважительной причине.
Я воспроизвел ваш пример во всех подробностях, чтобы подтвердить проблему и теорию о причине.
Привязка ComboBox к CurrentPerson не использует оператор равенства для нахождения соответствия ЕСЛИ ВЫ ИСПОЛЬЗУЕТЕ SelectedValue INSTEAD OF SelectedItem . Если вы остановите свой override bool Equals(object obj)
, вы увидите, что при изменении выбора оно не будет достигнуто.
Изменяя свой установщик на следующий, вы находите конкретный соответствующий объект, используя ваш оператор Equals, так что последующее сравнение значений 2 объектов будет работать.
set
{
if (m_AvailablePersons.Contains(value)) {
m_Person = m_AvailablePersons.Where(p => p.Equals(value)).First();
}
else throw new ArgumentOutOfRangeException("value");
NotifyPropertyChanged("CurrentPerson");
}
Теперь действительно интересный результат:
Даже если вы измените свой код на использование SelectedItem, он будет работать для обычной привязки к списку, но все равно не будет работать для любой привязки к отсортированному представлению!
Я добавил отладочный вывод в метод Equals, и хотя совпадения были найдены, они были проигнорированы:
public override bool Equals(object obj)
{
if (obj is Person)
{
Person other = obj as Person;
if (other.Firstname == Firstname && other.Surname == Surname)
{
Debug.WriteLine(string.Format("{0} == {1}", other.ToString(), this.ToString()));
return true;
}
else
{
Debug.WriteLine(string.Format("{0} <> {1}", other.ToString(), this.ToString()));
return false;
}
}
return base.Equals(obj);
}
Мой вывод ...
... заключается в том, что за кулисами ComboBox находит совпадение, но из-за наличия CollectionViewSource между ним и необработанными данными он затем игнорирует сопоставление и сравнивает объекты вместо этого (чтобы решить, какой из них был выбран) , Из памяти CollectionViewSource управляет собственным текущим выбранным элементом, , поэтому, если вы не получите точное соответствие объекта, он никогда не будет работать с использованием CollectionViewSource с ComboxBox .
По сути, ваше изменение сеттера работает, потому что оно гарантирует совпадение объектов в CollectionViewSource, которое затем гарантирует совпадение объектов в ComboBox.
Тестовый код
Полный тестовый код приведен ниже для тех, кто хочет играть (извините за взлом кода, но это было только для тестирования, а не MVVM).
Просто создайте новое приложение Silverlight 4 и добавьте следующие файлы / изменения:
PersonViewModel.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
namespace PersonTests
{
public class PersonViewModel : INotifyPropertyChanged
{
private Person m_Person = null;
private readonly ObservableCollection<Person> m_AvailablePersons =
new ObservableCollection<Person>(new List<Person> {
new Person("Mike", "Smith"),
new Person("Jake", "Jackson"),
new Person("Anne", "Aardvark"),
});
public ObservableCollection<Person> AvailablePersons
{
get { return m_AvailablePersons; }
}
public Person CurrentPerson
{
get { return m_Person; }
set
{
if (m_Person != value)
{
m_Person = value;
NotifyPropertyChanged("CurrentPerson");
}
}
//set // This works
//{
// if (m_AvailablePersons.Contains(value)) {
// m_Person = m_AvailablePersons.Where(p => p.Equals(value)).First();
// }
// else throw new ArgumentOutOfRangeException("value");
// NotifyPropertyChanged("CurrentPerson");
//}
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Person
{
public string Firstname { get; set; }
public string Surname { get; set; }
public Person(string firstname, string surname)
{
this.Firstname = firstname;
this.Surname = surname;
}
public override string ToString()
{
return Firstname + " " + Surname;
}
public override bool Equals(object obj)
{
if (obj is Person)
{
Person other = obj as Person;
if (other.Firstname == Firstname && other.Surname == Surname)
{
Debug.WriteLine(string.Format("{0} == {1}", other.ToString(), this.ToString()));
return true;
}
else
{
Debug.WriteLine(string.Format("{0} <> {1}", other.ToString(), this.ToString()));
return false;
}
}
return base.Equals(obj);
}
}
}
MainPage.xaml
<UserControl x:Class="PersonTests.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:scm="clr-namespace:System.ComponentModel;assembly=System.Windows" mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<CollectionViewSource x:Key="PersonsViewSource" Source="{Binding AvailablePersons}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Surname" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Background="LightBlue" Width="150">
<!--<ComboBox ItemsSource="{Binding AvailablePersons}"
SelectedItem="{Binding Path=CurrentPerson, Mode=TwoWay}" />-->
<ComboBox ItemsSource="{Binding Source={StaticResource PersonsViewSource}}"
SelectedItem="{Binding Path=CurrentPerson, Mode=TwoWay}" />
<Button Content="Select Mike Smith" Height="23" Name="button1" Click="button1_Click" />
<Button Content="Select Anne Aardvark" Height="23" Name="button2" Click="button2_Click" />
</StackPanel>
</UserControl>
MainPage.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace PersonTests
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new PersonViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as PersonViewModel).CurrentPerson = new Person("Mike", "Smith");
}
private void button2_Click(object sender, RoutedEventArgs e)
{
(this.DataContext as PersonViewModel).CurrentPerson = new Person("Anne", "Aardvark");
}
}
}