Подтвердить что ты не робот

WPF MVVM INotifyPropertyChanged Implementation - модель или ViewModel

Я прочитал ряд дебатов о том, где реализовать INotifyPropertyChanged здесь в StackOverflow и других блогах, но кажется, что есть случаи, когда вы должны реализовать его на модели. Вот мой сценарий - я ищу обратную связь по моему заключению или мой подход ошибочен.

Я использую эту реализацию ObservableDictionary (ObservableDictionary), потому что мне нужны исполняемые запросы с помощью ключа.

В этом словаре я помещаю коллекцию объектов Model.

В моей виртуальной машине я объявляю экземпляр (Книги) словаря и привязки к нему 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>  

Если я реализую INotifyPropertyChanged в виртуальной машине для книг и изменяю значение имени книги в коде, пользовательский интерфейс не обновляется.

Если я реализую INotifyPropertyChanged в VM для Store и изменяю значение имени книги в коде, пользовательский интерфейс не обновляется.

Если я реализую INotifyProperyChanged на модели и изменяю значение имени книги в коде, пользовательский интерфейс обновляется.

Событие с измененным значением не запускается в первом случае, потому что средство для настройки словаря не вызывается, это элемент (книга).

Мне что-то не хватает, потому что, если это правильная интерпретация, если я хочу, чтобы последовательные уведомления для моих моделей были независимо от того, связаны ли они непосредственно с XAML или через какую-то коллекцию, я бы всегда хотел, чтобы модель реализовала INotifyProperyChanged.

Btw, помимо ссылки dll, я лично не вижу INotifyPropertyChanged как функцию пользовательского интерфейса - думаю, что он должен быть определен в более общем пространстве имен .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>

Как указано выше, когда я нажимаю кнопку и изменяю значение в первой книге, пользовательский интерфейс не изменяется.

Однако, когда я перемещаю INotifyPropertyChanged в Model, он отлично работает (обновления пользовательского интерфейса), потому что изменение находится в настройке свойств модели не в книгах в 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 в модели?

Спасибо.

4b9b3361

Ответ 1

Дело в том, что если вы следовали за MVVM, у вас был бы BookViewModel для вашего класса модели Book. Таким образом, у вас будет реализация 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;
    }
}

Как вы можете видеть, когда вы обновляете свойство Title Book, вы будете делать это через свойство Title соответствующей модели представления, которая приведет к событию PropertyChanged, которое вызовет обновление пользовательского интерфейса.

Ответ 2

Прочитайте эту статью. В нем объясняется, как можно уменьшить дублирование кода, реализовав INotifyPropertyChanged в модели.

Ответ 3

Не путайте INotifyPropertyChanged с MVVM.

Подумайте, что на самом деле представляет собой INotifyPropertyChanged → Это событие, которое стреляет сказать "Эй, посмотри, я изменился". Если кто-то заботится, то они могут что-то с этим сделать, будь то View, ViewModel или что-то еще.

Итак, начнем с вашей книги (модели). Свойство Title может запускать измененное событие, почему бы и нет? Это имеет смысл, Книга имеет дело с собственными свойствами.

Теперь для BookViewModel - отлично, нам не нужно дублировать заголовок и заполнять наш код! Ого!

Рассмотрим вид, в котором мы хотим увидеть список книг или книгу со списком авторов. Ваша ViewModel может обрабатывать дополнительные свойства, специфичные для представления, такие как IsSelected. Это отличный пример: почему книга была бы осторожна, если бы она была выбрана или нет? Это ответственность за ViewModel.


Очевидно, что это зависит от вашей архитектуры, но лично, если я создаю библиотеку объектов, я реализую базовый класс с INotifyPropertyChanged и сделаю свойства объекта ответственными за запуск события.