我已经阅读过关于在StackOverflow和其他博客上实现INotifyPropertyChanged的地方的一些争论,但似乎有些情况下你必须在模型上实现它。这是我的情景 - 我正在寻找关于我的结论的反馈或我的方法是错误的。
我正在使用ObservableDictionary(ObservableDictionary)的这个实现,因为我需要使用密钥进行高效的查询。
在这本词典中,我放置了模型对象的集合。
在我的虚拟机中,我声明了字典的实例(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>
如果我在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; }
}
数据提供程序生成一些虚拟数据
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移动到模型时,它工作正常(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的情况下实现这一目标?
感谢。
答案 0 :(得分:21)
问题在于,如果您关注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));
}
}
}
您的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;
}
}
正如您所看到的,当您更新Title
的{{1}}属性时,您将通过相应视图模型的Book
属性执行此操作,该属性将引发Title
1}} event,它将触发UI更新。
答案 1 :(得分:8)
请阅读this article。它解释了如何通过在模型中实现INotifyPropertyChanged
来减少代码重复。
答案 2 :(得分:5)
不要将INotifyPropertyChanged与MVVM混淆。
考虑INotifyPropertyChanged实际上是什么 - &gt;这是一个激动说“嘿看,我已经改变”的事件。如果有人关心那么他们可以做些什么,无论他们是View,ViewModel还是其他什么。
让我们从你的书(模型)开始吧。 Title属性可以触发已更改的事件,为什么不呢?这是有意义的,本书正在处理它自己的属性。
现在对于BookViewModel - 太棒了,我们不需要复制标题并增加我们的代码!喔!
考虑一个我们想要查看书籍列表的视图,或者一本包含作者列表的书籍。您的ViewModel可以处理特定于视图的其他属性,例如IsSelected。这是一个很好的例子 - 为什么本书会关心它是否被选中?这是ViewModel的责任。
显然上面取决于你的体系结构,但是如果我正在创建一个对象库,我将使用INotifyPropertyChanged实现一个基类,并使对象属性负责触发事件。