WPF MVVM INotifyPropertyChanged Реализация - Модель или ViewModel - PullRequest
27 голосов
/ 12 марта 2011

Я прочитал ряд дискуссий о том, где реализовать 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 на виртуальной машине для хранилища и изменю значение имени книги в коде, пользовательский интерфейс не будет обновлен.

Если я реализую 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;
    }
}

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

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 голосов
/ 12 марта 2011

Дело в том, что если бы вы следили за 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событие, которое вызовет обновление пользовательского интерфейса.

8 голосов
/ 09 мая 2011

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

5 голосов
/ 27 ноября 2014

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

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

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

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

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


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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...