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

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

问题描述:

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

I have read a number of debates on where to implement INotifyPropertyChanged here on * 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.

我正在使用 ObservableDictionary (ObservableDictionary),因为我需要使用密钥进行高性能查询.

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

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

In this dictionary I place the collection of Model objects.

在我的 VM 中,我声明了字典的一个实例 (Books) 并在 XAML 中绑定到它.

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

    <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 上实现 INotifyPropertyChanged 并在代码中更改图书名称的值,则 UI 不会更新.

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 上为 Store 实现 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.

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

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

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

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

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

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.

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

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.

编辑从这里开始:

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

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.

模型:

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 代码通常不会这样 - 仅举个简单的例子

XAML code behind Would not normally to it this way - just for simple example

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 不会改变.

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

但是,当我将 INotifyPropertyChanged 移动到模型时,它工作正常(UI 更新),因为更改是在模型属性设置器中而不是在 VM 中:

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:

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 的​​情况下实现这一点?

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

谢谢.

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

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

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

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

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

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

BookViewModel 我的意思是别的.您需要在此视图模型中包装的不是 Book 对象的整个集合,而是单独的 Book:

By BookViewModel I meant something else. You need to wrap in this view model not the whole collection of Book objects but an individual 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 而不是 ObservableCollection:

And your BookProvider will return ObservableCollection<BookViewModel> instead of 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;
    }
}

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

As you can see, when you are updating the Title property of the Book you will be doing it through the Title property of the corresponding view model that will raise the PropertyChanged event, which will trigger the UI update.