web-dev-qa-db-ja.com

WPF MVVM INotifyPropertyChanged実装-モデルまたはViewModel

ここでStackOverflowや他のブログでINotifyPropertyChangedを実装する場所について多くの議論を読みましたが、モデルに実装する必要がある場合があるようです。ここに私のシナリオがあります-私は自分の結論に関するフィードバックを探しているか、私のアプローチが間違っています。

このObservableDictionaryの実装( ObservableDictionary )を使用しています。これは、キーを使用したパフォーマンスクエリが必要だからです。

この辞書には、Modelオブジェクトのコレクションを配置します。

VMでは、辞書のインスタンス(Books)を宣言し、XAMLでそれにバインドします。

    <tk:DataGrid AutoGenerateColumns="False" Grid.Row="1" ItemsSource="{Binding Mode=TwoWay, Path=Books.Store}" Grid.ColumnSpan="2" Margin="3">
        <tk:DataGrid.Columns>
            <tk:DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=Value.Name}" MinWidth="100" Header="Name" />
            <tk:DataGridTextColumn Binding="{Binding Mode=TwoWay, Path=Value.Details}" MinWidth="300" Header="Details" />
        </tk:DataGrid.Columns>        
    </tk:DataGrid>  

BooksのVMにINotifyPropertyChangedを実装し、コードでBook名の値を変更すると、UIは更新されません。

StoreのVMにINotifyPropertyChangedを実装し、コードでブック名の値を変更すると、UIは更新されません。

モデルにINotifyProperyChangedを実装し、コードでブック名の値を変更すると、UIが更新されます。

最初のケースでは、ディクショナリセッターが呼び出されず、アイテム(ブック)が呼び出されるため、Changedイベントは発生しません。

これが正しい解釈である場合、XAMLから直接バインドされているか何らかのコレクションを介してバインドされているかに関係なく、モデルに一貫した通知が必要な場合、常にINotifyProperyChangedをモデルに実装する必要があるため、何かが欠けていますか?.

ところで、dll参照のほかに、私は個人的にINotifyPropertyChangedをUI関数として見ていません-より一般的な.net名前空間で定義すべきだと思います-私の2セント。

ここから編集を開始:

セマンティクスの議論が非常に盛んであったため、質問の核心を見逃してしまったので、ここに再度掲載しますが、非常に単純なMVVMの例で質問を説明します。

モデル:

public class Book
{
    public string Title { get; set; )
    public List<Author> Authors { get; set; }
}

public class Author
{
    public string Name { get; set; }
}

ダミーデータを生成するデータプロバイダー

public class BookProvider
{
    public ObservableCollection<Book> GetBooks() {
        ObservableCollection<Book> books = new ObservableCollection<Book>();

        books.Add(new Book {
            Title = "Book1",
            Authors = new List<Author> { new Author { Name = "Joe" }, new Author { Name = "Phil" } }
        });

        books.Add(new Book {
            Title = "Book2",
            Authors = new List<Author> { new Author { Name = "Jane" }, new Author { Name = "Bob" } }
        });

        return books;
    }
}

ViewModel

    public class BookViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Book> books;
    public ObservableCollection<Book> Books {
        get { return books; }
        set {
            if (value != books) {
                books = value;
                NotifyPropertyChanged("Books");
            }
        }
    }

    private BookProvider provider;

    public BookViewModel() {
        provider = new BookProvider();
        Books = provider.GetBooks();
    }

    // For testing the example
    public void MakeChange() {
        Books[0].Title = "Changed";
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(String info) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

XAMLコードビハインド通常、この方法ではそうではありません-単純な例

public partial class MainWindow : Window
{
    private BookViewModel vm;

    public MainWindow() {
        InitializeComponent();

        vm = new BookViewModel();
        this.DataContext = vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
        vm.MakeChange();
    }
}

XAML

<Window x:Class="BookTest.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="242*" />
        <RowDefinition Height="69*" />
    </Grid.RowDefinitions>
    <ListBox ItemsSource="{Binding Books}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="{Binding Title}" />
                    <ListBox ItemsSource="{Binding Authors}">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}" FontStyle="Italic" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                    </ListBox>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Grid.Row="1" Content="Change" Click="Button_Click" />
</Grid>

上記のように、ボタンをクリックして最初のブックの値を変更しても、UIは変わりません。

ただし、INotifyPropertyChangedをモデルに移動すると、VMのブックではなくモデルプロパティセッターに変更があるため、正常に動作します(UI更新)。

public class Book : INotifyPropertyChanged
{
    private string title;
    public string Title {
        get { return title; }
        set {
            if (value != title) {
                title = value;
                NotifyPropertyChanged("Title");
            }
        }
    }

    public List<Author> Authors { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(String info) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

元の質問に戻って、モデルにINotifyPropertyChangedを実装せずにこれを達成するにはどうすればよいですか?

ありがとう。

27
IUnknown

MVVMをフォローしている場合、BookViewModelモデルクラスにBookが必要になるということです。したがって、そのビューモデルにはINotifyPropertyChanged実装があります。まさにその目的のためにMVVMが存在します(しかしそれだけではありません)。

つまり、INotifyPropertyChangedは、モデルではなく、ビューモデルクラスに実装する必要があります。

[〜#〜] update [〜#〜]:あなたの更新とコメントでの議論に応えて...

BookViewModelによって私は何か他のものを意味しました。このビューモデルでは、Bookオブジェクトのコレクション全体ではなく、個々のBookをラップする必要があります。

public class BookViewModel : INotifyPropertyChanged
{
    private Book book;

    public Book Book {
        get { return book; }    
    }

    public string Title {
        get { return Book.Title; }
        set {
            Book.Title = value;
            NotifyPropertyChanged("Title");
        }         
    }

    public BookViewModel(Book book) {
        this.book = book;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void NotifyPropertyChanged(String info) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

そして、BookProviderObservableCollection<BookViewModel>ではなくObservableCollection<Book>を返します:

public class BookProvider
{
    public ObservableCollection<BookViewModel> GetBooks() {
        ObservableCollection<BookViewModel> books = new ObservableCollection<BookViewModel>();

        books.Add(new BookViewModel(new Book {
            Title = "Book1",
            Authors = new List<Author> { new Author { Name = "Joe" }, new Author { Name = "Phil" } }
        }));

        books.Add(new BookViewModel(new Book {
            Title = "Book2",
            Authors = new List<Author> { new Author { Name = "Jane" }, new Author { Name = "Bob" } }
        }));

        return books;
    }
}

ご覧のとおり、TitleBookプロパティを更新するときは、UI更新をトリガーするTitleイベントを発生させる対応するビューモデルのPropertyChangedプロパティを使用してそれを実行します。

21
Pavlo Glazkov

この記事 をお読みください。モデルにINotifyPropertyChangedを実装することにより、コードの重複を減らす方法を説明します。

8
Jayesh Shah

INotifyPropertyChangedとMVVMを混同しないでください。

INotifyPropertyChangedが実際に何であるかを考慮してください->これは、「ちょっと見て、変わった」と言うために起動するイベントです。誰かが気にすれば、View、ViewModel、その他何でも、それについて何かをすることができます。

それでは、ブック(モデル)から始めましょう。 Titleプロパティは変更されたイベントを発生させることができますが、なぜ発生しないのでしょうか?それは理にかなっています、本はそれ自身の特性を扱っています。

BookViewModelの場合-タイトルを複製してコードを一括する必要はありません。おー!

書籍のリスト、または著者のリストを含む書籍を表示するビューを検討します。 ViewModelは、IsSelectedなど、ビューに固有の追加プロパティを処理できます。これは素晴らしい例です-ブックが選択されたかどうかを気にするのはなぜですか?これはViewModelの責任です。


明らかに上記はあなたのアーキテクチャに依存しますが、個人的にオブジェクトライブラリを作成する場合は、基本クラスをINotifyPropertyChangedで実装し、オブジェクトプロパティがイベントを発生させるようにします。

5
pfeds