15

単純な VM クラスがあるとしましょう

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");
            }
        }
    }

たとえば、次のように ComboBox に正常にデータバインドするだけで十分です。

<ComboBox ItemsSource="{Binding AvailablePersons}" 
          SelectedValue="{Binding Path=CurrentPerson, Mode=TwoWay}" />

Person がEqualsオーバーロードされていることに注意してください。ViewModel で CurrentPerson 値を設定すると、コンボボックスの現在の項目に新しい値が表示されます。

ここで、ビューに並べ替え機能を追加したいとしましょうCollectionViewSource

 <UserControl.Resources>
        <CollectionViewSource x:Key="PersonsViewSource" Source="{Binding AvailablePersons}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="Surname" Direction="Ascending" />
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
    </UserControl.Resources>

コンボボックス アイテムのソース バインディングは次のようになります。

<ComboBox ItemsSource="{Binding Source={StaticResource PersonsViewSource}}"
          SelectedValue="{Binding Path=CurrentPerson, Mode=TwoWay}" />    

そして、それは実際にソートされます(さらにアイテムを追加すると、明らかに表示されます)。

ただし、CurrentPerson現在 VM を変更すると (以前は CollectionView を使用せずにクリア バインディングを使用すると正常に動作していました)、この変更はバインドされた ComboBox に表示されません。

その後、VM から CurrentItem を設定するには、何らかの方法でビューにアクセスし (MVVM で ViewModel からビューに移動しないでください)、MoveCurrentToメソッドを呼び出してビューに currentItem の表示を強制的に変更する必要があると思います。

そのため、追加のビュー機能 (ソート) を追加することで、期待される動作ではないと思われる既存の viewModel への TwoWay バインディングが失われました。

ここで TwoWay バインディングを保持する方法はありますか? あるいは、私が間違ったことをしたのかもしれません。

編集:実際には、CurrentPerson セッターを次のように書き換えると、状況はより複雑になります。

set
{
    if (m_AvailablePersons.Contains(value)) {
       m_Person = m_AvailablePersons.Where(p => p.Equals(value)).First();
    }
    else throw new ArgumentOutOfRangeException("value");
    NotifyPropertyChanged("CurrentPerson");

}

それは動作しfineます!

そのバグのある動作、または説明はありますか? オーバーロードされていても、いくつかの理由で、person オブジェクトの参照の等価性Equalsが必要です。

参照の等価性が必要な理由が本当にわからないので、メソッドがオーバーロードされているときに通常のセッターが機能しない理由を説明できる人に報奨金Equalを追加しています。これは、それを使用する「修正」コードで明確に見られます

4

4 に答える 4

11

あなたには 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");

        }
    }
}
于 2011-06-24T11:45:08.713 に答える
1

CollectionView の使用を検討し、コンボボックスに IsSynchronizedWithCurrentItem を設定しましたか?

それが私がすることです - あなたの CurrentPerson プロパティを持つ代わりに、あなたの collectionView.CurrentItem に選択された人があり、collectionview の currentitem に続くコンボボックスがあります。

コレクションビューを問題なく並べ替えとグループ化で使用しました-UIからの適切な分離が得られます。

コレクションビューをコードに移動し、そこにバインドします

public ICollectionView AvailablePersonsView {get;プライベート セット;}

で:

AvailablePersonsView = CollectionViewSource.GetDefaultView(AvailablePersons)

于 2011-06-27T09:04:01.707 に答える
0

TwoWayバインディングは正常に機能しますが、設定時またはコードからComboBoxのUIでは自動的に更新されません。この機能が必要な場合は、継承元を拡張してリッスンするか、初期選択のみを設定する場合は、で実行します。SelectedItemSelectedIndexComboBoxSelectionChangedSelectorLoaded

于 2011-06-10T11:51:44.200 に答える
0

Microsoft の Kyle McClellan による ComboBoxExtensions の使用を強くお勧めします

XAML で ComboBox のデータソースを宣言できます。これは、はるかに柔軟で、非同期モードで使用できます。

基本的に、解決策は、ComboBox に CollectionViewSource を使用しないことです。サーバー側のクエリで並べ替えを行うことができます。

于 2012-05-21T20:00:39.797 に答える