MVVM в WPF - как предупреждать ViewModel об изменениях в модели… или мне следует?

112

Я просматриваю некоторые статьи о MVVM, в основном это и это .

Мой конкретный вопрос: как передать изменения модели из модели в модель просмотра?

В статье Джоша я не вижу, чтобы он этим занимался. ViewModel всегда запрашивает у модели свойства. В примере Рэйчел у нее есть реализация модели INotifyPropertyChangedи она вызывает события из модели, но они предназначены для потребления самим представлением (см. Ее статью / код для более подробной информации о том, почему она это делает).

Нигде я не вижу примеров, когда модель предупреждает ViewModel об изменениях свойств модели. Меня это беспокоит, что, возможно, это не сделано по какой-то причине. Есть ли шаблон для предупреждения ViewModel об изменениях в Модели? Это может показаться необходимым, поскольку (1) возможно существует более 1 ViewModel для каждой модели, и (2) даже если существует только одна ViewModel, некоторые действия с моделью могут привести к изменению других свойств.

Я подозреваю, что могут быть ответы / комментарии в форме «Зачем вам это нужно?» комментарии, так что вот описание моей программы. Я новичок в MVVM, поэтому, возможно, весь мой дизайн ошибочен. Кратко опишу.

Я программирую что-то более интересное (по крайней мере, для меня!), Чем классы «Клиент» или «Продукт». Я программирую блэкджек.

У меня есть представление, которое не имеет никакого кода и просто полагается на привязку к свойствам и командам в модели представления (см. Статью Джоша Смита).

Хорошо это или плохо , но я придерживался мнения, что Модель должна содержать не только классы, такие как PlayingCard,, Deckно и BlackJackGameкласс, который сохраняет состояние всей игры и знает, когда игрок обанкротился, дилер должен взять карты и текущий счет игрока и дилера (меньше 21, 21, перебор и т. д.).

Из BlackJackGameя раскрываю такие методы, как «DrawCard», и мне пришло в голову, что при отрисовке карты такие свойства, как CardScore, и, IsBustдолжны быть обновлены, и эти новые значения передаются в ViewModel. Возможно, это ошибочное мышление?

Можно было бы подумать, что ViewModel вызывал DrawCard()метод, поэтому он должен знать, что нужно запрашивать обновленную оценку и выяснять, разоряется он или нет. Мнения?

В моей ViewModel у меня есть логика, позволяющая захватить реальное изображение игральной карты (в зависимости от масти, ранга) и сделать его доступным для просмотра. Модель не должна беспокоиться об этом (возможно, другая ViewModel будет просто использовать числа вместо изображений игральных карт). Конечно, возможно, кто-то скажет мне, что Модель не должна даже иметь концепции игры в Блэкджек, и что это следует обрабатывать в ViewModel?

Дэйв
источник
3
Взаимодействие, которое вы описываете, звучит как стандартный механизм событий - это все, что вам нужно. Модель может предоставлять вызываемое событие OnBust, и виртуальная машина может подписаться на него. Думаю, вы также можете использовать подход ИЭА.
code4life
Честно говоря, если бы я мог создать настоящее «приложение» для блэкджека, мои данные были бы спрятаны за несколькими уровнями сервисов / прокси и педантичным уровнем юнит-тестов, подобных A + B = C. Это был бы прокси. / сервис, информирующий об изменениях.
Мейрион Хьюз
1
Спасибо всем! К сожалению, я могу выбрать только один ответ. Я выбираю Рэйчел из-за дополнительных советов по архитектуре и устранения исходного вопроса. Но было много отличных ответов, и я ценю их. -Dave
Дэйв
2
FWIW: После нескольких лет борьбы со сложностями поддержки концепции виртуальных машин и M для каждого домена, теперь я считаю, что наличие обоих не дает DRY; необходимое разделение задач может быть осуществлено более легко, имея два ИНТЕРФЕЙСА на одном объекте - «Интерфейс домена» и «Интерфейс модели представления». Этот объект можно передать как в бизнес-логику, так и в логику просмотра без путаницы или отсутствия синхронизации. Этот объект является «объектом идентичности» - он однозначно представляет сущность. Для сохранения разделения кода домена и кода представления необходимы более совершенные инструменты для этого внутри класса.
ToolmakerSteve

Ответы:

61

Если вы хотите, чтобы ваши модели предупреждали ViewModels об изменениях, они должны реализовать INotifyPropertyChanged , а ViewModels должны подписаться на получение уведомлений PropertyChange.

Ваш код может выглядеть примерно так:

// Attach EventHandler
PlayerModel.PropertyChanged += PlayerModel_PropertyChanged;

...

// When property gets changed in the Model, raise the PropertyChanged 
// event of the ViewModel copy of the property
PlayerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "SomeProperty")
        RaisePropertyChanged("ViewModelCopyOfSomeProperty");
}

Но обычно это необходимо только в том случае, если несколько объектов будут вносить изменения в данные модели, что обычно не так.

Если у вас когда-либо был случай, когда у вас фактически нет ссылки на свойство модели, чтобы присоединить к нему событие PropertyChanged, вы можете использовать систему обмена сообщениями, такую ​​как Prism EventAggregatorили MVVM Light Messenger.

В моем блоге есть краткий обзор систем обмена сообщениями, однако, чтобы подвести итог, любой объект может транслировать сообщение, и любой объект может подписаться на прослушивание определенных сообщений. Таким образом, вы можете транслировать a PlayerScoreHasChangedMessageот одного объекта, а другой объект может подписаться на прослушивание этих типов сообщений и обновлять его PlayerScoreсвойство, когда он слышит одно.

Но я не думаю, что это нужно для описанной вами системы.

В идеальном мире MVVM ваше приложение состоит из ваших ViewModels, а ваши модели - это всего лишь блоки, используемые для создания вашего приложения. Обычно они содержат только данные, поэтому у них не будет таких методов, как DrawCard()(это было бы в ViewModel)

Таким образом, у вас, вероятно, будут простые объекты данных модели, подобные этим:

class CardModel
{
    int Score;
    SuitEnum Suit;
    CardEnum CardValue;
}

class PlayerModel 
{
    ObservableCollection<Card> FaceUpCards;
    ObservableCollection<Card> FaceDownCards;
    int CurrentScore;

    bool IsBust
    {
        get
        {
            return Score > 21;
        }
    }
}

и у вас будет объект ViewModel, например

public class GameViewModel
{
    ObservableCollection<CardModel> Deck;
    PlayerModel Dealer;
    PlayerModel Player;

    ICommand DrawCardCommand;

    void DrawCard(Player currentPlayer)
    {
        var nextCard = Deck.First();
        currentPlayer.FaceUpCards.Add(nextCard);

        if (currentPlayer.IsBust)
            // Process next player turn

        Deck.Remove(nextCard);
    }
}

(Все вышеперечисленные объекты должны быть реализованы INotifyPropertyChanged, но я оставил это для простоты)

Рэйчел
источник
3
В общем, вся ли бизнес-логика / правила включены в модель? Куда идет вся логика, которая гласит, что вы можете взять карту до 21 (но дилер остается на 17), что вы можете разделить карты и т. Д. Я предположил, что все это относится к модельному классу, и по этой причине я чувствовал, что мне нужен класс контроллера BlacJackGame в модели. Я все еще пытаюсь понять это и буду благодарен за примеры / ссылки. Идея блэкджека для примера была взята из класса iTunes по программированию iOS, где бизнес-логика / правила определенно находятся в классе модели шаблона MVC.
Дэйв
3
@Dave Да, DrawCard()метод будет во ViewModel вместе с другой игровой логикой. В идеальном приложении MVVM вы должны иметь возможность запускать свое приложение полностью без пользовательского интерфейса, просто создав ViewModels и запустив их методы, например, с помощью тестового сценария или окна командной строки. Модели обычно представляют собой только модели данных, содержащие необработанные данные и проверку основных данных.
Рэйчел
6
Спасибо Рэйчел за помощь. Мне придется изучить это еще немного или написать другой вопрос; Я до сих пор не понимаю, где находится игровая логика. Вы (и другие) выступаете за то, чтобы поместить его в ViewModel, другие говорят, что «бизнес-логика», которая в моем случае, как я полагаю, является правилом игры и состоянием игры, принадлежащими модели (см., Например: msdn.microsoft.com/en-us /library/gg405484%28v=pandp.40%29.aspx ) и stackoverflow.com/questions/10964003/… ). Я понимаю, что в этой простой игре это, вероятно, не имеет большого значения. Но было бы неплохо узнать. Thxs!
Дэйв
1
@Dave Я могу неправильно использовать термин «бизнес-логика» и смешивать его с логикой приложения. Чтобы процитировать статью MSDN, которую вы связали, «Чтобы максимизировать возможности повторного использования, модели не должны содержать какого-либо специфического для варианта использования или пользовательской задачи поведения или логики приложения» и «Как правило, модель представления будет определять команды или действия, которые могут быть представлены в пользовательском интерфейсе и который может вызывать пользователь " . Так что такие вещи, как a, DrawCardCommand()должны быть во ViewModel, но я думаю, у вас может быть BlackjackGameModelобъект, содержащий DrawCard()метод, который вызывается командой, если вы хотите
Рэйчел
2
Избегайте утечек памяти. Используйте шаблон WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/...
JJS
24

Короткий ответ: это зависит от специфики.

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

  1. Viewmodel создается и обертывает модель
  2. Viewmodel подписывается на PropertyChangedсобытие модели
  3. Viewmodel устанавливается как view DataContext, свойства привязаны и т. Д.
  4. Просмотр действия триггеров на модели просмотра
  5. Viewmodel вызывает метод на модели
  6. Сама модель обновляется
  7. Viewmodel обрабатывает модели PropertyChangedи PropertyChangedв ответ поднимает свои собственные
  8. View отражает изменения в своих привязках, замыкая цикл обратной связи

С другой стороны, если ваши модели содержат мало (или не содержат) бизнес-логики, или если по какой-либо другой причине (например, для получения транзакционных возможностей) вы решили позволить каждой модели представления «владеть» своей обернутой моделью, тогда все модификации модели будут проходить через модель просмотра, поэтому в таком расположении нет необходимости.

Я описываю такой дизайн в другом вопросе MVVM здесь .

Джон
источник
Здравствуйте, список, который вы составили, великолепен. Однако у меня проблема с 7. и 8. В частности: у меня есть ViewModel, который не реализует INotifyPropertyChanged. Он содержит список дочерних элементов, который содержит сам список дочерних элементов (он используется как ViewModel для элемента управления WPF Treeview). Как заставить UserControl DataContext ViewModel «слушать» изменения свойств в любом из дочерних элементов (TreeviewItems)? Как именно мне подписаться на все дочерние элементы, которые реализуют INotifyPropertyChanged? Или стоит сделать отдельный вопрос?
Игорь
4

Ваш выбор:

  • Реализовать INotifyPropertyChanged
  • События
  • POCO с прокси-манипулятором

На мой взгляд, INotifyPropertyChangedэто фундаментальная часть .Net. т.е. его в System.dll. Реализация этого в вашей «Модели» сродни реализации структуры событий.

Если вам нужен чистый POCO, вам нужно эффективно манипулировать своими объектами через прокси / сервисы, а затем ваша ViewModel уведомляется об изменениях, слушая прокси.

Лично я просто реализую INotifyPropertyChanged, а затем использую FODY, чтобы делать за меня грязную работу. Это выглядит и ощущается POCO.

Пример (использование FODY для IL Weave the PropertyChanged Raisers):

public class NearlyPOCO: INotifyPropertyChanged
{
     public string ValueA {get;set;}
     public string ValueB {get;set;}

     public event PropertyChangedEventHandler PropertyChanged;
}

тогда вы можете заставить вашу ViewModel прослушивать PropertyChanged на предмет любых изменений; или изменения, связанные с недвижимостью.

Прелесть маршрута INotifyPropertyChanged в том, что вы связываете его с расширенной коллекцией ObservableCollection . Итак, вы сбрасываете свои близкие объекты poco в коллекцию и слушаете коллекцию ... если что-то изменится, вы узнаете об этом где угодно.

Честно говоря, это могло бы присоединиться к обсуждению «Почему INotifyPropertyChanged автоматически не обрабатывался компилятором», который переходит к следующему: Каждый объект в C # должен иметь возможность уведомлять, если какая-либо его часть была изменена; т.е. реализовать INotifyPropertyChanged по умолчанию. Но это не так, и лучший способ, требующий наименьших усилий, - использовать IL Weaving (в частности, FODY ).

Мейрион Хьюз
источник
4

Довольно старый поток, но после долгих поисков я придумал собственное решение: PropertyChangedProxy

С помощью этого класса вы можете легко зарегистрироваться в чьем-то другом NotifyPropertyChanged и предпринять соответствующие действия, если он запущен для зарегистрированного свойства.

Вот пример того, как это могло бы выглядеть, когда у вас есть свойство модели «Статус», которое может изменяться само по себе, а затем должно автоматически уведомлять ViewModel о запуске собственного PropertyChanged для его свойства «Статус», чтобы представление также получало уведомление: )

public class MyModel : INotifyPropertyChanged
{
    private string _status;
    public string Status
    {
        get { return _status; }
        set { _status = value; OnPropertyChanged(); }
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class MyViewModel : INotifyPropertyChanged
{
    public string Status
    {
        get { return _model.Status; }
    }

    private PropertyChangedProxy<MyModel, string> _statusPropertyChangedProxy;
    private MyModel _model;
    public MyViewModel(MyModel model)
    {
        _model = model;
        _statusPropertyChangedProxy = new PropertyChangedProxy<MyModel, string>(
            _model, myModel => myModel.Status, s => OnPropertyChanged("Status")
        );
    }

    // Default INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

а вот и сам класс:

/// <summary>
/// Proxy class to easily take actions when a specific property in the "source" changed
/// </summary>
/// Last updated: 20.01.2015
/// <typeparam name="TSource">Type of the source</typeparam>
/// <typeparam name="TPropType">Type of the property</typeparam>
public class PropertyChangedProxy<TSource, TPropType> where TSource : INotifyPropertyChanged
{
    private readonly Func<TSource, TPropType> _getValueFunc;
    private readonly TSource _source;
    private readonly Action<TPropType> _onPropertyChanged;
    private readonly string _modelPropertyname;

    /// <summary>
    /// Constructor for a property changed proxy
    /// </summary>
    /// <param name="source">The source object to listen for property changes</param>
    /// <param name="selectorExpression">Expression to the property of the source</param>
    /// <param name="onPropertyChanged">Action to take when a property changed was fired</param>
    public PropertyChangedProxy(TSource source, Expression<Func<TSource, TPropType>> selectorExpression, Action<TPropType> onPropertyChanged)
    {
        _source = source;
        _onPropertyChanged = onPropertyChanged;
        // Property "getter" to get the value
        _getValueFunc = selectorExpression.Compile();
        // Name of the property
        var body = (MemberExpression)selectorExpression.Body;
        _modelPropertyname = body.Member.Name;
        // Changed event
        _source.PropertyChanged += SourcePropertyChanged;
    }

    private void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _modelPropertyname)
        {
            _onPropertyChanged(_getValueFunc(_source));
        }
    }
}
Ремер
источник
1
Избегайте утечек памяти. Используйте шаблон WeakEvent. joshsmithonwpf.wordpress.com/2009/07/11/...
JJS
1
@JJS - OTOH, считайте, что шаблон слабого события опасен . Лично я бы предпочел рискнуть утечкой памяти, если забуду unregister ( -= my_event_handler), потому что это легче отследить, чем редкую и непредсказуемую проблему зомби, которая может или не может когда-либо произойти.
ToolmakerSteve
@ToolmakerSteve благодарит за добавление сбалансированного аргумента. Я предлагаю разработчикам делать для них лучшее в их собственной ситуации. Не перенимайте слепо исходный код из Интернета. Существуют и другие шаблоны, такие как обычно используемый межкомпонентный обмен сообщениями EventAggregator / EventBus (которые также имеют свои собственные риски)
JJS
2

Я нашел эту статью полезной: http://social.msdn.microsoft.com/Forums/vstudio/en-US/3eb70678-c216-414f-a4a5-e1e3e557bb95/mvvm-businesslogic-is-part-of-the-?forum = wpf

Мое резюме:

Идея организации MVVM состоит в том, чтобы упростить повторное использование представлений и моделей, а также обеспечить независимое тестирование. Ваша модель представления - это модель, которая представляет сущности представления, ваша модель представляет бизнес-объекты.

Что, если вы захотите сыграть в покер позже? Большую часть пользовательского интерфейса следует использовать повторно. Если ваша игровая логика привязана к вашей модели представления, будет очень сложно повторно использовать эти элементы без необходимости перепрограммировать модель представления. Что делать, если вы хотите изменить свой пользовательский интерфейс? Если ваша игровая логика связана с логикой вашей модели представления, вам нужно будет перепроверить, что ваша игра все еще работает. Что, если вы хотите создать рабочий стол и веб-приложение? Если ваша модель представления содержит логику игры, будет сложно поддерживать эти два приложения бок о бок, поскольку логика приложения неизбежно будет связана с бизнес-логикой в ​​модели представления.

Уведомления об изменении данных и проверка данных происходят на каждом уровне (представление, модель представления и модель).

Модель содержит ваши представления данных (сущности) и бизнес-логику, специфичную для этих сущностей. Колода карт - это логическая «вещь» с присущими ей свойствами. В хорошую колоду нельзя класть повторяющиеся карты. Он должен показать способ получения верхней карты (ов). Ему нужно знать, что нельзя выдавать больше карточек, чем осталось. Такое поведение колоды является частью модели, потому что оно присуще колоде карт. Также будут модели дилеров, модели игроков, модели рук и т.д. Эти модели могут и будут взаимодействовать.

Модель представления будет состоять из логики представления и приложения. Вся работа, связанная с отображением игры, отделена от логики игры. Это может включать отображение рук в виде изображений, запросы карт к модели дилера, настройки отображения пользователя и т. Д.

Внутренности статьи:

По сути, я люблю объяснять это тем, что ваша бизнес-логика и сущности составляют модель. Это то, что использует ваше конкретное приложение, но может использоваться во многих приложениях.

Представление - это уровень представления - все, что связано с непосредственным взаимодействием с пользователем.

ViewModel - это, по сути, «клей», специфичный для вашего приложения, который связывает их вместе.

У меня есть хорошая диаграмма, показывающая, как они взаимодействуют:

http://reedcopsey.com/2010/01/06/better-user-and-developer-experiences-from-windows-forms-to-wpf-with-mvvm-part-7-mvvm/

В вашем случае - давайте разберемся с некоторыми особенностями ...

Валидация: обычно бывает двух форм. Проверка, связанная с пользовательским вводом, будет происходить в ViewModel (в первую очередь) и в представлении (то есть: «Числовое» текстовое поле, предотвращающее ввод текста, обрабатывается для вас в представлении и т. Д.). Таким образом, проверка ввода от пользователя обычно является проблемой виртуальной машины. При этом часто существует второй «уровень» проверки - это проверка того, что используемые данные соответствуют бизнес-правилам. Это часто является частью самой модели - когда вы отправляете данные в свою модель, это может вызвать ошибки проверки. Затем виртуальной машине придется переназначить эту информацию обратно в представление.

Операции «за кулисами без просмотра, такие как запись в БД, отправка электронной почты и т. Д.»: Это действительно часть «конкретных операций домена» на моей диаграмме, и на самом деле это чисто часть модели. Это то, что вы пытаетесь раскрыть через приложение. ViewModel действует как мост для предоставления этой информации, но операции являются чисто модельными.

Операции для ViewModel: ViewModel требует больше, чем просто INPC - ей также нужны любые операции, специфичные для вашего приложения (а не для вашей бизнес-логики), такие как сохранение настроек и состояния пользователя и т. Д. Это будет зависеть от приложения. по приложению, даже при сопряжении с одной и той же "моделью".

Хороший способ подумать об этом - предположим, вы хотите создать две версии своей системы заказов. Первый находится в WPF, а второй - это веб-интерфейс.

Общая логика, которая имеет дело с самими заказами (отправка электронных писем, ввод в БД и т. Д.) - это Модель. Ваше приложение предоставляет пользователю эти операции и данные, но делает это двумя способами.

В приложении WPF пользовательский интерфейс (то, с чем взаимодействует средство просмотра) является «представлением» - в веб-приложении это в основном код, который (по крайней мере, в конечном итоге) превращается в javascript + html + css на клиенте.

ViewModel - это остальная часть «связующего звена», необходимого для адаптации вашей модели (эти операции, связанные с упорядочением), чтобы заставить ее работать с конкретной технологией / слоем представления, которую вы используете.

ГолосоватьКофе
источник
Может быть, простой пример - музыкальный проигрыватель. Ваши модели будут содержать библиотеки, активный звуковой файл, кодеки, логику проигрывателя и код обработки цифрового сигнала. Модели представления будут содержать ваши элементы управления, визуализации и браузер библиотеки. для отображения всей этой информации требуется много логики пользовательского интерфейса, и было бы неплохо позволить одному программисту сосредоточиться на воспроизведении музыки, а другому программисту - сделать пользовательский интерфейс интуитивно понятным и увлекательным. Модель представления и модель должны позволить этим двум программистам согласовать набор интерфейсов и работать отдельно.
VoteCoffee
Еще один хороший пример - веб-страница. Логика на стороне сервера обычно эквивалентна модели. Логика на стороне клиента обычно эквивалентна модели представления. Я легко мог бы предположить, что игровая логика будет принадлежать серверу, а не быть доверена клиенту.
VoteCoffee
2

Уведомление на основе INotifyPropertyChanged и INotifyCollectionChanged именно то , что вам нужно. Чтобы упростить себе жизнь с подпиской на изменения свойств, проверкой имени свойства во время компиляции и предотвращением утечек памяти, я бы посоветовал вам использовать PropertyObserver от Josh Smith's MVVM Foundation . Поскольку это проект с открытым исходным кодом, вы можете добавить только этот класс в свой проект из источников.

Чтобы понять, как пользоваться PropertyObserver, прочтите эту статью .

Кроме того, обратите внимание на Reactive Extensions (Rx) . Вы можете открыть IObserver <T> из своей модели и подписаться на него в модели представления.

Владимир Дорохов
источник
Большое спасибо за ссылку на отличную статью Джоша Смита и за освещение слабых событий!
JJS
1

Ребята проделали потрясающую работу, отвечая на это, но в подобных ситуациях я действительно чувствую, что шаблон MVVM - это боль, поэтому я бы пошел и использовал подход Supervising Controller или Passive View и отпустил систему привязки, по крайней мере, для объектов модели, которые генерируют изменения сами по себе.

Ибрагим Наджар
источник
1

Я уже давно защищаю направленную Модель -> Модель просмотра -> Просмотр потока изменений, как вы можете видеть в разделе « Поток изменений » моей статьи о MVVM от 2008 года. Это требует реализации INotifyPropertyChangedв модели. Насколько я могу судить, с тех пор это стало обычной практикой.

Поскольку вы упомянули Джоша Смита, взгляните на его класс PropertyChanged . Это вспомогательный класс для подписки на INotifyPropertyChanged.PropertyChangedсобытие модели .

На самом деле вы можете пойти дальше в этом подходе, поскольку недавно я создал свой класс PropertiesUpdater . Свойства в модели представления вычисляются как сложные выражения, которые включают в себя одно или несколько свойств модели.

HappyNomad
источник
1

Нет ничего плохого в том, чтобы реализовать INotifyPropertyChanged внутри модели и слушать его внутри ViewModel. Фактически, вы можете даже указать свойство модели прямо в XAML: {Binding Model.ModelProperty}

Что касается зависимых / вычисляемых свойств, доступных только для чтения, то я пока не видел ничего лучше и проще: https://github.com/StephenCleary/CalculatedProperties . Это очень просто, но невероятно полезно, на самом деле это «формулы Excel для MVVM» - работает так же, как Excel распространяет изменения в ячейки формул без дополнительных усилий с вашей стороны.

Кола
источник
0

Вы можете вызывать события из модели, на которые модель просмотра должна подписаться.

Например, я недавно работал над проектом, для которого мне нужно было создать древовидное представление (естественно, модель имела иерархический характер). В модели у меня была наблюдаемая коллекция под названием ChildElements.

В модели просмотра я сохранил ссылку на объект в модели и подписался на CollectionChangedсобытие наблюдаемой коллекции, например: ModelObject.ChildElements.CollectionChanged += new CollectionChangedEventHandler(insert function reference here)...

Затем ваша модель просмотра получает автоматическое уведомление об изменении в модели. Вы можете использовать ту же концепцию PropertyChanged, но вам нужно будет явно вызывать события изменения свойств из вашей модели, чтобы это работало.

Маш
источник
Если вы имеете дело с иерархическими данными, вам стоит взглянуть на демонстрацию 2 моей статьи о MVVM .
HappyNomad
0

Мне кажется, что это действительно важный вопрос - даже когда нет никакого давления. Я работаю над тестовым проектом, который включает TreeView. Есть пункты меню и тому подобное, которые отображаются на команды, например, Удалить. В настоящее время я обновляю и модель, и модель представления из модели представления.

Например,

public void DeleteItemExecute ()
{
    DesignObjectViewModel node = this.SelectedNode;    // Action is on selected item
    DocStructureManagement.DeleteNode(node.DesignObject); // Remove from application
    node.Remove();                                // Remove from view model
    Controller.UpdateDocument();                  // Signal document has changed
}

Это просто, но, похоже, имеет очень серьезный недостаток. Типичный модульный тест выполняет команду, а затем проверяет результат в модели представления. Но это не означает, что обновление модели было правильным, поскольку они обновляются одновременно.

Поэтому, возможно, лучше использовать такие методы, как PropertyObserver, чтобы позволить обновлению модели запускать обновление модели представления. Один и тот же модульный тест теперь будет работать, только если оба действия будут успешными.

Я понимаю, что это не потенциальный ответ, но, похоже, его стоит опубликовать.

Искусство
источник