MVVM у WPF - Як попередити ViewModel про зміни в Моделі… чи я повинен?


112

Я переглядаю деякі статті MVVM, в першу чергу це і це .

Моє конкретне питання: Як я можу повідомити про зміни в моделі від моделі до ViewModel?

У статті Джоша я не бачу, що він це робить. ViewModel завжди запитує у Моделі властивості. На прикладі Рейчел у неї є реалізована модель INotifyPropertyChangedта викликає події з моделі, але вони призначені для споживання самим видом (див. Її статтю / код для отримання більш детальної інформації про те, чому вона це робить).

Ніде я не бачу прикладів, коли модель попереджає ViewModel про зміни властивостей моделі. Це мене хвилює, що, можливо, це не робиться з якихось причин. Чи існує схема оповіщення ViewModel про зміни в Моделі? Це може здатися необхідним, оскільки (1) можливо, існує більше 1 ViewModel для кожної моделі, і (2) навіть якщо є лише один ViewModel, деякі дії на моделі можуть призвести до зміни інших властивостей.

Я підозрюю, що можуть бути відповіді / коментарі форми "Чому б ти хотів це зробити?" коментарі, ось ось опис моєї програми. Я новачок у MVVM, тому, можливо, весь мій дизайн несправний. Я коротко опишу це.

Я програмую щось цікаве (принаймні, для мене!), Ніж класи "Клієнт" або "Продукт". Я програмую BlackJack.

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

На краще чи гірше, я подумав, що Модель повинна містити не лише класи, такі як PlayingCard, Deckале, а також BlackJackGameклас, який підтримує стан всієї гри, і знає, коли гравець перекинувся, дилер повинен намалювати карти, і який поточний бал гравця та дилера (менше 21, 21, бюст тощо).

З цього BlackJackGameя викриваю такі методи, як "DrawCard", і мені спало на думку, що коли малюється картка, такі властивості, як CardScore, і IsBustповинні бути оновлені, і ці нові значення передаються ViewModel. Можливо, це помилкове мислення?

Можна поставитись до того, що ViewModel назвав DrawCard()метод, тож він повинен знати, щоб попросити оновлений бал і дізнатись, чи є він чи ні. Думки?

У моєму ViewModel я маю логіку схопити фактичне зображення ігрової карти (на основі костюму, ранжу) та зробити її доступною для перегляду. Модель не повинна цим займатися (можливо, інші ViewModel просто використовуватимуть цифри замість того, щоб грати на карти). Звичайно, можливо, хтось скаже мені, що Модель навіть не повинна мати концепцію гри BlackJack і з цим слід обробляти в ViewModel?


3
Взаємодія, яку ви описуєте, звучить як стандартний механізм подій - це все, що вам потрібно. Модель може викрити подію, яку називають OnBust, і VM може підписатися на неї. Я думаю, ви також могли використовувати підхід МЕА.
code4life

Буду чесно, якщо я де зробити справжній блекджек "додаток", мої дані будуть приховані за декількома шарами сервісів / проксі-серверів і педантичним рівнем одиничних тестів, схожих на A + B = C. Це був би проксі / послуга, яка інформує про зміни.
Meirion Hughes

1
Дякую всім! На жаль, я можу вибрати лише одну відповідь. Я вибираю Рейчел завдяки додатковим порадам щодо архітектури та прибирання оригінального питання. Але було багато чудових відповідей, і я їх ціную. -Dave
Дейв


2
FWIW: Протягом декількох років, що боролися зі складностями підтримання як VM, так і M на доменну концепцію, я вважаю, що мати обоє не вдасться DRY; необхідне розділення проблем може бути легше, якщо на одному об'єкті є два ІНТЕРФЕЙСИ - "Доменний інтерфейс" та "Інтерфейс ViewModel". Цей об'єкт може передаватися як бізнес-логіці, так і логіці перегляду, без плутанини або відсутності синхронізації. Цей об’єкт є "об'єктом ідентичності" - він однозначно представляє сутність. Підтримуючи розділення доменного коду від коду перегляду, тоді потрібні кращі інструменти для цього всередині класу.
ToolmakerSteve

Відповіді:


61

Якщо ви хочете, щоб ваші Моделі попередили ViewModels про зміни, вони повинні впровадити INotifyPropertyChanged , а ViewModels повинні підписатися на отримання сповіщень PropertiesChange .

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

// 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");
}

Але, як правило, це потрібно лише в тому випадку, якщо більш ніж один об’єкт буде вносити зміни до даних Моделі, що зазвичай не буває.

Якщо у вас коли-небудь є випадок, коли ви фактично не маєте посилання на своє властивість Model, щоб приєднати до нього подія PropertyChanged, ви можете використовувати таку систему обміну повідомленнями, як Prism's EventAggregatorабо MVVM Light Messenger.

Я маю короткий огляд систем обміну повідомленнями у своєму блозі, однак підсумовуючи це, будь-який об’єкт може транслювати повідомлення, а будь-який об’єкт може підписатися на прослуховування конкретних повідомлень. Таким чином, ви можете транслювати 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 встановлюється як перегляд DataContext, властивості пов'язані тощо
  4. Перегляд запускає дії на viewmodel
  5. Метод виклику Viewmodel на моделі
  6. Модель оновлює себе
  7. Viewmodel справляється з моделями PropertyChangedі PropertyChangedреагує на власну відповідь
  8. Перегляд відображає зміни в його прив'язках, закриваючи цикл зворотного зв'язку

З іншого боку, якщо ваші моделі містили мало (або ні) ділової логіки, або якщо з якоїсь іншої причини (наприклад, набуття трансакційного потенціалу) ви вирішили дозволити кожному перегляду моделей "мати" свою обернуту модель, то всі модифікації моделі пройдуть через viewmodel, тому така домовленість не була б необхідною.

Я описую таку конструкцію в іншому запитанні MVVM тут .


Привіт, список, який ви склали, є геніальним. Однак у мене є проблеми з 7. і 8. Зокрема: у мене є ViewModel, який не реалізує INotifyPropertyChanged. Він містить список дітей, який містить список самих дітей (використовується як ViewModel для керування Treeview WPF). Як змусити UserControl DataContext ViewModel "прослухати" зміни властивостей будь-якого з дітей (TreeviewItems)? Як саме я підписатись на всі дочірні елементи, які реалізують INotifyPropertyChanged? Або я повинен поставити окреме запитання?
Ігор

4

Ваш вибір:

  • Впровадити INotifyPropertyChanged
  • Події
  • POCO з маніпулятором проксі

Як я бачу, INotifyPropertyChangedце фундаментальна частина .Net. тобто його в System.dll. Реалізація його у вашій "Моделі" схожа на реалізацію структури подій.

Якщо ви хочете отримати чистий POCO, вам ефективно доведеться маніпулювати своїми об'єктами через проксі-сервіси / сервіси, і тоді ваш ViewModel буде повідомлений про зміни, прослуховуючи проксі.

Особисто я просто вільно реалізую INotifyPropertyChanged, а потім використовую FODY, щоб зробити брудну роботу для мене. Це виглядає і відчувається POCO.

Приклад (використовуючи FODY для IL Weave підйомників PropertyChanged):

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

     public event PropertyChangedEventHandler PropertyChanged;
}

тоді ви можете змусити ваш ViewModel прослухати PropertyChanged для будь-яких змін; або зміни, пов'язані з властивістю.

Краса маршруту INotifyPropertyChanged - це зв'язати його за допомогою Extended ObservableCollection . Таким чином, ви скидаєте близькі об'єкти poco в колекцію і слухаєте колекцію ... якщо щось зміниться, де завгодно, ви дізнаєтесь про це.

Чесно кажу, це може приєднатися до дискусії "Чому не вдалося автоматично обробляти компілятор INotifyPropertyChanged", яка переходить до: Кожен об'єкт в c # повинен мати можливість повідомляти про зміну будь-якої його частини; тобто впроваджуйте INotifyPropertyChanged за замовчуванням. Але це не найкращий маршрут, який вимагає найменших зусиль, - це використання IL Weaving (конкретно FODY ).


4

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

За допомогою цього класу ви можете легко зареєструватися в чужому NotifyPropertyChanged і вжити відповідних дій, якщо він запускається для зареєстрованого ресурсу.

Ось зразок того, як це могло виглядати, коли у вас є властивість моделі "Статус", яка може змінюватися самостійно, а потім автоматично повідомляти ViewModel про запуск її власності PropertiesChanged на його "Status" властивості, щоб представлення також було повідомлено: )

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, вважайте, що схема слабких подій небезпечна . Особисто я вважаю за краще ризикувати витоком пам’яті, якщо забуду скасувати реєстрацію ( -= 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/

У вашому випадку - давайте вирішимо деякі особливості ...

Перевірка: Зазвичай це складається у 2 формах. Перевірка, пов’язана з введенням користувача, відбуватиметься в ViewModel (головним чином), а в View (тобто: "Числовий" TextBox, що запобігає введенню тексту, обробляється для вас у представленні даних тощо). Таким чином, перевірка вхідних даних від користувача, як правило, є проблемою VM. При цьому, часто існує другий "шар" перевірки - це перевірка відповідності даних, що використовуються, діловим правилам. Це часто є частиною самої моделі - коли ви натискаєте дані на свою Модель, це може спричинити помилки перевірки. Після цього В.М. доведеться перезавантажувати цю інформацію назад до Перегляду.

Операції "поза кадром без перегляду, як-от написання в БД, надсилання електронної пошти тощо": Це дійсно частина "Доменних операцій" на моєму діаграмі, і справді є суто частиною Моделі. Це те, що ви намагаєтесь викрити через додаток. ViewModel виступає мостом для викриття цієї інформації, але операції є чисто Модельними.

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

Хороший спосіб подумати над цим - скажіть, що ви хочете зробити 2 версії системи замовлення. Перший - у WPF, а другий - веб-інтерфейс.

Загальна логіка, яка стосується самих замовлень (надсилання електронних листів, введення в БД тощо) - це Модель. Ваша програма відкриває користувачеві ці операції та дані, але робить це двома способами.

У програмі WPF користувальницький інтерфейс (з чим взаємодіє глядач) - це "view" - у веб-додатку це в основному код, який (принаймні зрештою) перетворюється на javascript + html + css на клієнті.

ViewModel - це решта «клею», необхідного для адаптації вашої моделі (ці операції, пов’язані з замовленням), щоб вона працювала з певною технологією / шаром перегляду, яку ви використовуєте.


Можливо, простий приклад - музичний плеєр. Ваші моделі міститимуть бібліотеки та активний звуковий файл та кодеки, логіку програвача та код цифрового сигналу. Моделі представлення містять ваші елементи керування та візуалізації та браузер бібліотеки. Існує багато логіки інтерфейсу, необхідної для відображення всієї цієї інформації, і було б непогано дозволити одному програмісту зосередитись на тому, щоб грати музику, дозволяючи іншому програмісту зосередитись на тому, щоб зробити інтерфейс інтуїтивно зрозумілим та цікавим. Вид-модель і модель повинні дозволяти цим двом програмістам домовлятися про набір інтерфейсів і працювати окремо.
VoteCoffee

Ще один хороший приклад - веб-сторінка. Логіка на сервері, як правило, еквівалентна моделі. Логіка на стороні клієнта, як правило, еквівалентна моделі перегляду. Я легко уявляю, що логіка гри належатиме на сервері і не буде доручена клієнту.
VoteCoffee

2

Повідомлення на основі INotifyPropertyChanged та INotifyCollectionChanged - саме те, що вам потрібно. Щоб спростити своє життя з підпискою на зміни властивостей, під час перевірки імені власності, під час компіляції, уникнення витоків пам’яті, я б радив вам використовувати PropertyObserver від MVVM Foundation Джоша Сміта . Оскільки цей проект є відкритим кодом, ви можете додати саме цей клас до свого проекту з джерел.

Щоб зрозуміти, як користуватися PropertyObserver, прочитайте цю статтю .

Також погляньте глибше на Реактивне розширення (Rx) . Ви можете виставити IObserver <T> зі своєї моделі та передплатити її у переглянутій моделі.


Дуже дякую за посилання на чудову статтю Джоша Сміта та висвітлення слабких подій!
JJS

1

Хлопці зробили дивовижну роботу, відповідаючи на це, але в таких ситуаціях я справді відчуваю, що MVVM-модель - це біль, тому я б пішов і використовував контрольований контролер або підхід пасивного перегляду і відпускав систему зв'язування хоча б для модельних об'єктів, які генерують зміни самостійно.


1

Я вже довго виступаю за модель спрямованості -> Перегляд моделі -> Перегляд потоку змін уже давно, як ви бачите в розділі « Потік змін» моєї статті MVVM від 2008 року. Це вимагає впровадженняINotifyPropertyChanged на моделі. Наскільки я можу сказати, це стало загальною практикою.

Оскільки ви згадали Джоша Сміта, подивіться на його клас PropertyChanged . Це допоміжний клас для підписки на INotifyPropertyChanged.PropertyChangedподію моделі .

Ви дійсно можете скористатися таким підходом набагато далі, як я нещодавно створив свій клас PropertiesUpdater . Властивості на моделі перегляду обчислюються як складні вирази, що включають одне або більше властивостей моделі.


1

Немає нічого поганого в застосуванні INotifyPropertyChanged всередині моделі та прослухати її всередині ViewModel. Насправді ви навіть можете вказати право власності на модель у XAML: {Binding Model.ModelProperty}

Що стосується залежних / обчислених властивостей лише для читання, я далеко не бачив нічого кращого і простішого за це: https://github.com/StephenCleary/CalculatedProperties . Це дуже просто, але неймовірно корисно, це дійсно "формули Excel для MVVM" - просто працює так само, як Excel розповсюджує зміни до комірок формули без зайвих зусиль з вашого боку.


0

Ви можете підняти події з моделі, на яку потрібно було б передплатити viewmodel.

Наприклад, я нещодавно працював над проектом, для якого мені довелося генерувати перегляд дерева (природно, модель мала йому ієрархічний характер). У моделі я мав спостережувануколекцію під назвою 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, щоб оновлення моделі викликало оновлення моделі перегляду. Те саме тестування одиниці працювало б лише в тому випадку, якщо обидві дії були успішними.

Я усвідомлюю, що це не потенційна відповідь, але, здається, варто її викласти там.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.