您遇到了 2 个问题,但您强调了将 CollectionViewSource 与 ComboBox 结合使用的真正问题。我仍在寻找以“更好的方式”解决此问题的替代方法,但是您的setter修复程序有充分的理由避免了该问题。
我已详细复制了您的示例,以确认问题和有关原因的理论。
如果您使用 SelectedValue而不是 SelectedItem ,则 ComboBox 绑定到 CurrentPerson 不使用等号运算符来查找匹配项。如果您设置断点,您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。
基本上,您的 setter 更改有效,因为它保证了 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);
}
}
}
主页.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");
}
}
}