あなたには 2 つの問題がありますが、ComboBox で CollectionViewSource を使用する際の実際の問題を強調しました。これを「より良い方法」で修正するための代替手段をまだ探していますが、セッターの修正は正当な理由で問題を回避します。
問題と原因についての理論を確認するために、あなたの例を詳細に再現しました。
CurrentPerson への ComboBox バインディングは、 SelectedItemの代わりに SelectedValueを使用する場合、一致を見つけるために equals 演算子を使用しません。ブレークポイントを設定する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 はメモリから、現在選択されている独自の項目を管理するため、正確に一致するオブジェクトが得られない場合は、ComboxBox で CollectionViewSource を使用しても機能しません。
基本的に、セッターの変更は 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");
}
}
}