WPF MVVM INotifyPropertyChanged实现-模型或视图模型
我在StackOverflow和其他博客上读到了一些关于在何处实现INotifyPropertyChanged的辩论,但似乎有些情况下您必须在模型上实现它。这是我的情况-我正在寻找关于我的结论的反馈,或者我的方法是错误的 我正在使用observedictionary()的这个实现,因为我需要使用键执行查询 在本词典中,我放置了模型对象的集合 在我的VM中,我声明字典的一个实例(Books)并在XAML中绑定到它WPF MVVM INotifyPropertyChanged实现-模型或视图模型,wpf,data-binding,mvvm,observablecollection,inotifypropertychanged,Wpf,Data Binding,Mvvm,Observablecollection,Inotifypropertychanged,我在StackOverflow和其他博客上读到了一些关于在何处实现INotifyPropertyChanged的辩论,但似乎有些情况下您必须在模型上实现它。这是我的情况-我正在寻找关于我的结论的反馈,或者我的方法是错误的 我正在使用observedictionary()的这个实现,因为我需要使用键执行查询 在本词典中,我放置了模型对象的集合 在我的VM中,我声明字典的一个实例(Books)并在XAML中绑定到它 <tk:DataGrid AutoGenerateColumns="F
<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>
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
{
私有字符串标题;
公共字符串标题{
获取{返回标题;}
设置{
如果(值!=标题){
标题=价值;
通知财产变更(“所有权”);
}
}
}
公共列表作者{get;set;}
公共事件属性更改事件处理程序属性更改;
受保护的void NotifyPropertyChanged(字符串信息){
if(PropertyChanged!=null){
PropertyChanged(此,新PropertyChangedEventArgs(信息));
}
}
}
那么回到我最初的问题,如何在不在模型中实现INotifyPropertyChanged的情况下实现这一点呢
谢谢。问题是,如果您在学习MVVM,您的
图书
模型类将有一个图书视图模型
。因此,您将在该视图模型上有一个INotifyPropertyChanged
实现。正是出于这个目的,MVVM的存在(但不仅限于此)
也就是说,INotifyPropertyChanged
必须在视图模型类而不是模型上实现
更新:针对您的更新和我们在评论中的讨论
我说的BookViewModel
是指别的东西。您需要在此视图模型中包装的不是整个Book
对象集合,而是单个Book
:
公共类BookViewModel:INotifyPropertyChanged
{
私人书籍;
公共图书{
获取{returnbook;}
}
公共字符串标题{
获取{return Book.Title;}
设置{
书名=价值;
通知财产变更(“所有权”);
}
}
公共图书视图模型(图书){
这本书;
}
公共事件属性更改事件处理程序属性更改;
受保护的void NotifyPropertyChanged(字符串信息){
if(PropertyChanged!=null){
PropertyChanged(此,新PropertyChangedEventArgs(信息));
}
}
}
而您的图书提供者
将返回可观察到的收藏
,而不是可观察到的收藏
:
公共类图书提供者
{
公共可观测集合GetBooks(){
ObservableCollection books=新的ObservableCollection();
books.Add(新建BookViewModel)(新建书本{
Title=“Book1”,
作者=新列表{新作者{Name=“Joe”},新作者{Name=“Phil”}
}));
books.Add(新建BookViewModel)(新建书本{
Title=“Book2”,
Authors=new List{new Author{Name=“Jane”},new Author{Name=“Bob”}
}));
还书;
}
}
如您所见,当您更新
书籍
的标题
属性时,您将通过相应视图模型的标题
属性进行更新,该属性将引发属性更改
事件,该事件将触发UI更新。请阅读。它解释了如何通过在模型中实现INotifyPropertyChanged
来减少代码重复。不要将INotifyPropertyChanged与MVVM混淆
考虑一下INotifyPropertyChanged实际上是什么->这是一个会触发说“嘿,看,我变了”的事件。如果有人关心,那么他们可以做些什么,不管是视图、视图模型还是其他什么
让我们从你的书(模型)开始。Title属性可以触发已更改的事件,为什么不呢?这是有道理的,这本书是在处理它自己的属性
现在对于BookViewModel-太好了,我们不需要复制标题和放大代码!哇
考虑一个视图,其中我们希望看到一个书籍列表,或者一个包含作者列表的书籍。ViewModel可以处理视图特定的其他属性,例如IsSelected。这是一个很好的例子——为什么这本书会在意它是否被选中?这是ViewModel的责任
显然,上述内容取决于您的体系结构,但就我个人而言,如果我正在创建一个对象库,我将使用INotifyPropertyChanged实现一个基类,并使对象属性负责触发事件。hmmm,我使用的是MVVM。我使用的是每个视图的ViewModel,而不是每个模型。ViewModel不仅包含视图绑定到的模型属性(有几个),还包含视图的命令实现。然后作为属性添加到视图的ViewModel以进行绑定的仅限于模型的BookViewModel可能只会执行INotifyPropertyChanged。为什么不在模型中这样做呢。@IUnknown-如果在模型中这样做,您将添加UI
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));
}
}
}
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();
}
}
<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>
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));
}
}
}