WPF MVVM INotifyPropertyChanged实现 - 模型或ViewModel

IUn*_*own 29 data-binding wpf observablecollection mvvm inotifypropertychanged

我已经在StackOverflow和其他博客上阅读了关于在何处实现INotifyPropertyChanged的一些争论,但似乎有些情况下你必须在Model上实现它.这是我的情景 - 我正在寻找关于我的结论的反馈或我的方法是错误的.

我正在使用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>  
Run Code Online (Sandbox Code Playgroud)

如果我在VM for Books上实现INotifyPropertyChanged并在代码中更改Book名称的值,则不会更新UI.

如果我在VM for Store上实现INotifyPropertyChanged并在代码中更改Book名称的值,则不会更新UI.

如果我在Model上实现INotifyProperyChanged并在代码中更改Book名称的值,则更新UI.

在第一种情况下不会触发Changed事件,因为未调用Dictionary setter,它的Item(a Book)是.

我错过了什么,因为如果这是正确的解释,如果我想要我的模型的一致通知,无论它们是直接来自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; }
}
Run Code Online (Sandbox Code Playgroud)

数据提供程序生成一些虚拟数据

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

后面的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

如上所述,当我单击按钮并更改第一本书中的值时,UI不会更改.

但是,当我将INotifyPropertyChanged移动到模型时,它工作正常(UI更新),因为更改位于模型属性设置器中而不是VM中的工作簿:

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

回到我原来的问题,如何在模型中不实现INotifyPropertyChanged的情况下实现这一目标?

谢谢.

Pav*_*kov 22

问题是,如果您关注MVVM,那么BookViewModel您的Book模型类就会有一个.因此,您将INotifyPropertyChanged在该视图模型上实现.正是出于这个目的,MVVM存在(但不仅仅是).

话虽这么说,INotifyPropertyChanged必须在视图模型类上实现,而不是模型.

更新:回应您的更新和我们在评论中的讨论......

通过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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你的BookProvider意志将返回ObservableCollection<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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,当您更新Title属性时,Book您将通过Title相应视图模型的属性来执行此操作,该属性将引发PropertyChanged事件,这将触发UI更新.


小智 8

请阅读这篇文章.它解释了如何通过INotifyPropertyChanged在模型中实现来减少代码重复.


pfe*_*eds 5

不要将INotifyPropertyChanged与MVVM混淆.

考虑一下INotifyPropertyChanged实际上是什么 - >这是一个激动的事件,说"嘿看,我已经改变了".如果有人关心,那么他们可以做些什么,无论他们是View,ViewModel还是其他什么.

那么让我们从你的书(模型)开始吧.Title属性可以触发已更改的事件,为什么不呢?这是有道理的,本书正在处理它自己的属性.

现在对于BookViewModel - 太棒了,我们不需要复制标题并增加我们的代码!喔!

考虑我们想要查看书籍列表的视图,或者带有作者列表的书籍.您的ViewModel可以处理特定于视图的其他属性,例如IsSelected.这是一个很好的例子 - 为什么本书会关心它是否被选中?这是ViewModel的责任.


显然上面取决于你的架构,但是如果我正在创建一个对象库,我将使用INotifyPropertyChanged实现一个基类,并使对象属性负责触发事件.