WPF MVVM INotifyPropertyChanged Реализация - Модель или ViewModel

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

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

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

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

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

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

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

Кстати, помимо ссылки на 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;
    }
}

Модель просмотра

    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>

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

Однако, когда я перемещаю INotifyPropertyChanged в модель, он отлично работает (обновления пользовательского интерфейса), потому что изменение находится в установщике свойств модели, а не в книгах в виртуальной машине:

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 в модели?

Спасибо.


person IUnknown    schedule 12.03.2011    source источник


Ответы (3)


Дело в том, что если бы вы следовали 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 объекта Book, вы будете делать это через свойство Title соответствующей модели представления, которое вызовет событие PropertyChanged, которое вызовет обновление пользовательского интерфейса.

person Pavlo Glazkov    schedule 12.03.2011
comment
хммм, я использую MVVM. Я использую ViewModel для просмотра, а не для модели. ViewModel не только содержит свойства модели, к которым привязывается View (их пара), но также и реализации команд для View. Модель BooksViewModel только для модели, которая затем будет добавлена ​​в качестве свойства к ViewModel представления для привязки, вероятно, будет реализовывать только INotifyPropertyChanged. Почему бы просто не сделать это в Модели. - person IUnknown; 13.03.2011
comment
@IUnknown - если вы сделаете это в модели, вы добавите элементы, специфичные для пользовательского интерфейса, в свои классы модели. Для этого есть модели просмотра. Эмпирическое правило MVVM - не иметь привязок к классам модели непосредственно в ваших представлениях. Так у вас не будет таких проблем. - person Pavlo Glazkov; 13.03.2011
comment
@Palvo - я не уверен, что понимаю тебя. У меня есть модели ViewModels, которые создают экземпляры моделей как свойства, подлежащие уведомлению, заполняют их из некоторого источника и привязывают (из xaml) пользовательский интерфейс к этим свойствам. Также обрабатывайте любую логику, специфичную для пользовательского интерфейса, например, реализацию команд. Разве это не привязывает пользовательский интерфейс к моим объектам модели (через свойство виртуальной машины)? Это возвращает меня к моему первоначальному вопросу. Если свойство виртуальной машины, к которому привязывается View, является ObservableDictionary, реализация IPropertyNotifyChanged виртуальной машины не обновляет пользовательский интерфейс, а модель обновляет. - person IUnknown; 13.03.2011
comment
@IUnknown - я говорю, что обычно в таких ситуациях вы создаете модель представления для элементов в вашем словаре, что означает, что вы помещаете свой объект Book в BookViewModel. Затем вы сможете вызвать событие PropertyChanged, когда это необходимо для этой модели представления. - person Pavlo Glazkov; 13.03.2011
comment
Таким образом, эта BookViewModel не является ViewModel для View, а скорее является еще одним слоем между ViewModel для View и Model. Я бы не хотел, чтобы это была ViewModel для View, потому что в ViewModel тоже происходят не книжные вещи. Назвать это ViewModel (BookViewModel) может сбивает с толку. Я обычно резервирую термин ViewModel для ViewModel и называю такие вещи, как ваша BookViewModel, по-прежнему моделями, потому что они либо просто объединяют модели вместе, либо обеспечивают управление коллекциями, но по-прежнему не зависят от того, к какому View они привязаны. - person IUnknown; 13.03.2011
comment
@IUnknown - На самом деле это модель представления. Не каждая модель представления должна иметь файл физического представления (файл xaml). В MVVM есть модель представления для элементов в коллекции. DataTemplate часто играет роль представления для таких моделей представления. - person Pavlo Glazkov; 13.03.2011
comment
Согласитесь, что не всегда бывает физический файл представления xaml, когда можно использовать DataTemplate, но обычно это более простые View / ViewModels. В случаях, когда представление является более сложным, включая пару обработчиков моделей и команд, ViewModel представляет и управляет этим для представления xaml. Я имею в виду именно этот случай. В этом случае, когда ViewModel поддерживает более сложный View, а не просто презентатор для простой модели, вы рекомендуете создать класс для обертывания BookModel для ViewModel View или реализовать INotifyPropertyChanged в модели - person IUnknown; 13.03.2011
comment
@IUnknown - я рекомендую создать класс для оболочки BookModel (вы можете называть его по-другому, но на самом деле это будет модель представления). - person Pavlo Glazkov; 13.03.2011
comment
Спасибо. Я согласен с тем, что мне нужно обернуть BookModel. Может быть, дело в семантике, но мне нравятся такие дебаты :). Я всегда думал, что ViewModel поддерживает View - только один аспект этого - представление модели. В моем случае BookModelView, который просто обертывает BookModel в коллекции и реализует INotifyPropertyChanged, недостаточен для поддержки View. Это означает, что у меня либо несколько уровней ViewModels с одной моделью, либо одна ViewModel и несколько уровней моделей. Думаю, я обычно отдавал предпочтение последнему. Во всяком случае, тот же чистый эффект, только личные предпочтения. - person IUnknown; 13.03.2011
comment
@Pavlo - извините за это снова - пожалуйста, посмотрите мои правки в исходном вопросе - person IUnknown; 13.03.2011
comment
@Palvo - Спасибо. Я понимаю класс BookViewModel, который вы указали выше. Это то, что я описал в одном из вышеприведенных комментариев - класс для обертывания модели, который реализует только INotifyPropertyChanged, поэтому Модель не обязана. Я предполагаю, что это связано с личными предпочтениями в дизайне, но если единственной причиной было добавить уведомление, я не вижу смысла в дополнительном уровне класса. Я действительно рассматриваю этот класс как способ изолировать ViewModel от изменений в Model. Продолжение в следующем комментарии .... - person IUnknown; 13.03.2011
comment
... Продолжение .... Я думаю, это также сводится (лично) к пространству имен INotifyPropertyChanged, которое заставляет нас думать, что мы добавляем код пользовательского интерфейса в модель. Я лично считаю, что INotifyPropertyChanged не имеет ничего общего с пользовательским интерфейсом и концептуально является просто способом уведомления объектов об их изменении - очень полезная возможность общего назначения. Жаль, что Microsoft поместила это в пространство имен, которое они сделали - IMO. - person IUnknown; 13.03.2011
comment
Кстати, если вы называете вышеупомянутую модель BookViewModel моделью представления, как вы называете класс, который я называю моделью представления? - person IUnknown; 13.03.2011
comment
@IUnknown - Послушайте, обе эти штуки - модели просмотра. Подумайте об этом так: все, что вы создаете для поддержки представления (например, реализация INotifyPropertyChanged), является моделью представления. Модель представления - это не только класс, который определяет команды и тому подобное. Все, что вы добавляете в классы своей модели только из-за представления (опять же, реализация INotifyPropertyChanged), должно переходить в модель представления. Наличие выделенной модели представления для каждого элемента в коллекции полезно, потому что в будущем вы также можете захотеть добавить в нее дополнительные элементы пользовательского интерфейса, например команды. - person Pavlo Glazkov; 13.03.2011

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

person Jayesh Shah    schedule 09.05.2011
comment
Гораздо более актуально. Почему класс модели не должен вызывать события уведомления при изменении свойств? Для меня имеет больше смысла. - person pfeds; 27.11.2014

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

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

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

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

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


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

person pfeds    schedule 27.11.2014