WPF MVVM INotifyPropertyChanged 实现 - 模型或视图模型

Posted

技术标签:

【中文标题】WPF MVVM INotifyPropertyChanged 实现 - 模型或视图模型【英文标题】:WPF MVVM INotifyPropertyChanged Implementation - Model or ViewModel 【发布时间】:2011-07-14 05:03:51 【问题描述】:

我在 *** 和其他博客上阅读了很多关于在哪里实现 INotifyPropertyChanged 的​​辩论,但似乎有些情况下您必须在模型上实现它。这是我的场景 - 我正在寻找关于我的结论的反馈,或者我的方法是否错误。

我正在使用 ObservableDictionary (ObservableDictionary) 的这种实现,因为我需要使用密钥进行高性能查询。

在这本字典中,我放置了模型对象的集合。

在我的 VM 中,我声明了字典的一个实例(书籍)并在 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>  

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

如果我在 VM 上为 Store 实现 INotifyPropertyChanged 并在代码中更改书名的值,则 UI 不会更新。

如果我在模型上实现 INotifyProperyChanged 并在代码中将值更改为书名,则 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; 

生成一些虚拟数据的数据提供者

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;
    

视图模型

    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 移动到模型时,它可以正常工作(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));
        
    

那么回到我最初的问题,如果不在模型中实现 INotifyPropertyChanged,我该如何做到这一点?

谢谢。

【问题讨论】:

【参考方案1】:

问题是,如果您关注 MVVM,您的 Book 模型类将有一个 BookViewModel。所以你会在那个视图模型上有一个INotifyPropertyChanged 实现。 MVVM 正是为了这个目的而存在的(但不仅如此)。

话虽如此,INotifyPropertyChanged 必须在视图模型类而不是模型上实现。

更新:响应您的更新和我们在 cmets 中的讨论...

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

您的BookProvider 将返回ObservableCollection&lt;BookViewModel&gt; 而不是ObservableCollection&lt;Book&gt;

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;
    

如您所见,当您更新BookTitle 属性时,您将通过相应视图模型的Title 属性进行更新,该属性将引发PropertyChanged 事件,该事件将触发用户界面更新。

【讨论】:

嗯,我正在使用 MVVM。我使用的是每个视图而不是每个模型的 ViewModel。 ViewModel 不仅包含 View 绑定到的 Model 属性(有几个),还包含 View 的命令实现。然后将作为属性添加到视图的 ViewModel 以进行绑定的仅模型 BooksViewModel 可能只会实现 INotifyPropertyChanged。为什么不在模型中这样做。 @IUnknown - 如果您在模型中执行此操作,您将在模型类中添加特定于 UI 的内容。有这方面的视图模型。 MVVM 中的经验法则不是直接在视图中绑定到模型类。这样你就不会遇到这种问题了。 @Palvo - 我不确定我是否关注你。我让我的 ViewModel 将模型的实例创建为“可通知”属性,从某个源填充它们,并将 UI 绑定(从 xaml)到这些属性。还可以处理任何特定于 UI 的逻辑,例如实现命令。这不是将 UI 绑定到我的模型对象(通过 VM 属性)吗?这让我回到我原来的问题。如果 View 绑定的 VM 属性是 ObservableDictionary,则 VM 的 IPropertyNotifyChanged 实现不会更新 UI,但模型会更新。 @IUnknown - 我的意思是,通常在这种情况下,您会为字典中的项目创建视图模型,这意味着您会将Book 对象包装在BookViewModel 中。然后,您将能够在该视图模型上适当地引发 PropertyChanged 事件。 所以这个 BookViewModel 不是 View 的 ViewModel,而是 View 的 ViewModel 和 Model 之间的另一层。我不希望它成为 View 的 ViewModel,因为 View 的 ViewModel 中也发生了“非书本”的事情。将其称为 ViewModel (BookViewModel) 似乎令人困惑。我通常为 View 的 ViewModel 保留术语 ViewModel 并将诸如 BookViewModel 之类的东西称为仍然模型,因为它们只是将模型聚合在一起或提供集合管理,但仍然不知道它们绑定到哪个视图。【参考方案2】:

请阅读this article。它解释了如何通过在模型中实现INotifyPropertyChanged 来减少代码重复。

【讨论】:

更有效。为什么模型类在属性更改时不应该引发通知事件。对我来说更有意义。【参考方案3】:

不要将 INotifyPropertyChanged 与 MVVM 混淆。

考虑一下 INotifyPropertyChanged 实际上是什么 -> 这是一个触发说“嘿,看,我已经改变了”的事件。如果有人关心,那么他们可以对此做点什么,无论他们是 View、ViewModel 还是其他。

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

现在是 BookViewModel - 太好了,我们不需要复制标题和增加我们的代码!哇!

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


显然,上述内容取决于您的架构,但就个人而言,如果我正在创建一个对象库,我将使用 INotifyPropertyChanged 实现一个基类,并使对象属性负责触发事件。

【讨论】:

以上是关于WPF MVVM INotifyPropertyChanged 实现 - 模型或视图模型的主要内容,如果未能解决你的问题,请参考以下文章

有没有wpf mvvm框架的详细讲解?

WPF MVVM

wpf mvvm获取列表行数据

MVVM设计模式和在WPF中的实现 事件绑定

wpf mvvm模式 Icommand接口应该如何理解?

WPF自学入门WPF MVVM模式Command命令