首页 文章

WPF MVVM INotifyPropertyChanged实现 - 模型或ViewModel

提问于
浏览
23

我已经在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>

如果我在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美分 .

EDIT STARTS HERE:

我们有一个很好的语义辩论,我错过了我的问题的核心,所以这里再次发布,但有一个非常简单的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

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 behind 通常不会这样 - 只是为了简单的例子

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

<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 回答

  • 21

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

    话虽这么说, INotifyPropertyChanged 必须在视图模型类上实现,而不是模型 .

    UPDATE :响应您的更新和我们在评论中的讨论......

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

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

  • 8

    请阅读this article . 它解释了如何通过在模型中实现 INotifyPropertyChanged 来减少代码重复 .

  • 5

    不要将INotifyPropertyChanged与MVVM混淆 .

    考虑一下INotifyPropertyChanged实际上是什么 - >这是一个激动的事件,说“嘿看,我已经改变了” . 如果有人关心,那么他们可以做些什么,无论他们是View,ViewModel还是其他什么 .

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

    现在对于BookViewModel - 太棒了,我们不需要复制 Headers 并增加我们的代码!喔!

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


    显然上面取决于你的架构,但是如果我正在创建一个对象库,我将使用INotifyPropertyChanged实现一个基类,并使对象属性负责触发事件 .

相关问题