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更新.
不要将INotifyPropertyChanged与MVVM混淆.
考虑一下INotifyPropertyChanged实际上是什么 - >这是一个激动的事件,说"嘿看,我已经改变了".如果有人关心,那么他们可以做些什么,无论他们是View,ViewModel还是其他什么.
那么让我们从你的书(模型)开始吧.Title属性可以触发已更改的事件,为什么不呢?这是有道理的,本书正在处理它自己的属性.
现在对于BookViewModel - 太棒了,我们不需要复制标题并增加我们的代码!喔!
考虑我们想要查看书籍列表的视图,或者带有作者列表的书籍.您的ViewModel可以处理特定于视图的其他属性,例如IsSelected.这是一个很好的例子 - 为什么本书会关心它是否被选中?这是ViewModel的责任.
显然上面取决于你的架构,但是如果我正在创建一个对象库,我将使用INotifyPropertyChanged实现一个基类,并使对象属性负责触发事件.