WPF MVVM INotifyPropertyChanged实现 - 模型或ViewModel

时间:2011-03-12 20:43:43

标签: wpf data-binding mvvm observablecollection inotifypropertychanged

我已经阅读过关于在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的情况下实现这一目标?

感谢。

3 个答案:

答案 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实现一个基类,并使对象属性负责触发事件。