WPF MVVM INotifyPropertyChanged實現-模型或ViewModel



data-binding observablecollection (2)

不要將INotifyPropertyChanged與MVVM混淆。

考慮一下INotifyPropertyChanged實際上是什麼 - >這是一個激動說“嘿看,我已經改變”的事件。 如果有人關心,那麼他們可以做些什麼,無論他們是View,ViewModel還是其他什麼。

那麼讓我們從你的書(模型)開始吧。 Title屬性可以觸發已更改的事件,為什麼不呢? 這是有道理的,本書正在處理它自己的屬性。

現在對於BookViewModel - 太棒了,我們不需要復制標題並增加我們的代碼! 喔!

考慮我們想要查看書籍列表的視圖,或者帶有作者列表的書籍。 您的ViewModel可以處理特定於視圖的其他屬性,例如IsSelected。 這是一個很好的例子 - 為什麼本書會關心它是否被選中? 這是ViewModel的責任。

顯然上面取決於你的架構,但是如果我正在創建一個對像庫,我將使用INotifyPropertyChanged實現一個基類,並使對象屬性負責觸發事件。

https://src-bin.com

我已經在StackOverflow和其他博客上閱讀了關於在何處實現INotifyPropertyChanged的一些爭論,但似乎有些情況你必須在模型上實現它。 這是我的情景 - 我正在尋找關於我的結論的反饋或我的方法是錯誤的。

我正在使用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美分。

編輯在這裡開始:

我們有一個很好的語義辯論,我錯過了我的問題的核心所以在這裡它再次發布,但有一個非常簡單的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的情況下實現這一目標?

謝謝。


Answer #1

問題是,如果您關注MVVM,您將擁有Book模型類的BookViewModel 。 因此,您將在該視圖模型上具有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;
    }
}

正如您所看到的,當您更新BookTitle屬性時,您將通過相應視圖模型的Title屬性執行此操作,該屬性將引發PropertyChanged事件,這將觸發UI更新。





inotifypropertychanged