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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5285377/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 22:39:35  来源:igfitidea点击:

WPF MVVM INotifyPropertyChanged Implementation - Model or ViewModel

wpfdata-bindingmvvmobservablecollectioninotifypropertychanged

提问by IUnknown

I have read a number of debates on where to implement INotifyPropertyChanged here on StackOverflow and other blogs but it seems that there are cases where you have to implement it on the Model. Here is my scenario - I am looking for feedback on my conclusion or is my approach wrong.

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

I am using this implementation of an ObservableDictionary (ObservableDictionary) because I need performant queries using the key.

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

In this dictionary I place the collection of Model objects.

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

In my VM, I declare an instance (Books) of the dictionary and in the XAML bind to it.

在我的 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>  

If I implement INotifyPropertyChanged on the VM for Books and change the value of a Book name in code, the UI is not updated.

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

If I implement INotifyPropertyChanged on the VM for Store and change the value of a Book name in code, the UI is not updated.

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

If I implement INotifyProperyChanged on the Model and change the value a Book name in code, the UI is updated.

如果我在模型上实现 INotifyProperyChanged 并在代码中更改书名的值,则 UI 会更新。

The Changed event is not fired in the first case because the Dictionary setter is not called, it's Item (a Book) is.

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

Am I missing something because if this is the correct interpretation, if I want consistent notifications for my Models regardless of whether they are bound to directly from XAML or via some sort of collection, I would always want the Model to implement INotifyProperyChanged.

我是否遗漏了什么,因为如果这是正确的解释,如果我想要我的模型一致的通知,无论它们是直接从 XAML 绑定还是通过某种集合绑定,我总是希望模型实现 INotifyProperyChanged。

Btw, besides the dll reference, I personally do no see INotifyPropertyChanged as a UI function - think it should be defined in a more general .net namespace - my 2 cents.

顺便说一句,除了 dll 引用,我个人没有将 INotifyPropertyChanged 视为 UI 函数 - 认为它应该在更通用的 .net 命名空间中定义 - 我的 2 美分。

EDIT STARTS HERE:

编辑从这里开始:

We were having such a good semantics debate that I missed the core of my question so here it is posted again but with a very simple MVVM example illustrate my question.

我们进行了如此精彩的语义辩论,以至于我错过了我的问题的核心,所以在这里再次发布,但用一个非常简单的 MVVM 示例来说明我的问题。

The Models:

模型:

public class Book
{
    public string Title { get; set; )
    public List<Author> Authors { get; set; }
}

public class Author
{
    public string Name { get; set; }
}

The Data Provider to generate some dummy data

Data Provider 生成一些虚拟数据

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

The 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 code behindWould not normally to it this way - just for simple example

后面的 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();
    }
}

The XAML

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>

As coded above, when I click on the button and change the value in the first Book, the UI does not change.

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

However, when I move the INotifyPropertyChanged to the Model it works fine (UI updates) because the change is in the Model property setter not Books in the VM:

但是,当我将 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));
        }
    }
}

So back to my original question, how do I accomplish this without implementing INotifyPropertyChanged in the Model?

那么回到我最初的问题,我如何在不在模型中实现 INotifyPropertyChanged 的​​情况下实现这一点?

Thanks.

谢谢。

采纳答案by Pavlo Glazkov

The thing is that if you were following MVVM, you would have a BookViewModelfor your Bookmodel class. So you would have a INotifyPropertyChangedimplementation on that view model. Exactly for that purpose MVVM exists (but not only).

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

That being said, the INotifyPropertyChangedhas to be implemented on view model classes, not models.

也就是说,INotifyPropertyChanged必须在视图模型类上实现,而不是模型。

UPDATE: In response to your update and our discussion in comments...

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

By BookViewModelI meant something else. You need to wrap in this view model not the whole collection of Bookobjects but an individual Book:

通过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));
        }
    }
}

And your BookProviderwill return ObservableCollection<BookViewModel>instead of ObservableCollection<Book>:

并且您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;
    }
}

As you can see, when you are updating the Titleproperty of the Bookyou will be doing it through the Titleproperty of the corresponding view model that will raise the PropertyChangedevent, which will trigger the UI update.

如您所见,当您更新 的Title属性时,Book您将通过Title将引发PropertyChanged事件的相应视图模型的属性进行更新,这将触发 UI 更新。

回答by Jayesh Shah

Please read this article. It explains how you can reduce code duplication by implementing INotifyPropertyChangedin the model.

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

回答by pfeds

Don't confuse INotifyPropertyChanged with MVVM.

不要将 INotifyPropertyChanged 与 MVVM 混淆。

Consider what INotifyPropertyChanged actually is -> It's an event that fires to say "Hey look, I've changed". If anyone cares then they can do something about it, whether they're a View, ViewModel, or whatever.

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

So let's start with your Book (Model). The Title property can fire a changed event, why would it not? It makes sense, the Book is dealing with it's own properties.

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

Now for BookViewModel - great, we don't need to duplicate the Title and bulk up our code! Whoo!

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

Consider a View where we want to see a list of books, or a book with a list of authors. Your ViewModel can handle additional properties specific for the view, such as IsSelected. This is a great example - why would the Book care if it was selected or not? This is the responsibility of the ViewModel.

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



Obviously the above depends on your architecture, but personally if I'm creating an object library I'll implement a base class with INotifyPropertyChanged and make the object properties responsible for firing the event.

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