Хороші приклади шаблону MVVM


141

Зараз я працюю з шаблоном Microsoft MVVM і виявляю, що відсутність детальних прикладів засмучує. У прикладі ContactBook показано дуже мало керованості командами, і єдиний інший приклад, який я знайшов, - це стаття журналу MSDN, де поняття схожі, але використовується дещо інший підхід і все ще не вистачає будь-якої складності. Чи є пристойні приклади MVVM, які принаймні показують основні операції з CRUD та перемикання діалогу / вмісту?


Пропозиції всіх були дуже корисні, і я почну складати список хороших ресурсів

Рамки / шаблони

Корисні статті

Екранні записи

Додаткові бібліотеки


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

Відповіді:


59

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

Випуск призми:
http://www.codeplex.com/CompositeWPF

Він включає в себе досить пристойний приклад програми (біржовий трейдер) разом з великою кількістю менших прикладів та способів. Принаймні, це хороша демонстрація декількох загальних підшаблонів, які люди використовують для того, щоб MVVM фактично працював. У них є приклади як для CRUD, так і для діалогів, я вважаю.

Призма не обов'язково для кожного проекту, але добре познайомитись.

CRUD: Ця частина досить проста, двосторонні прив’язки WPF полегшують редагування більшості даних. Справжній трюк - створити модель, яка спрощує налаштування інтерфейсу користувача. Принаймні, ви хочете переконатися, що ваш ViewModel (або бізнес-об’єкт) реалізує INotifyPropertyChangedдля підтримки прив'язки, і ви можете прив'язувати властивості прямо до елементів управління інтерфейсом, але ви можете також хотіти реалізувати IDataErrorInfoдля перевірки. Як правило, якщо ви використовуєте якесь рішення для ORM, налаштування CRUD є оснащенням.

У цій статті продемонстровано прості операції з грубого використання: http://dotnetslackers.com/articles/wpf/WPFDataBindingWithLINQ.aspx

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

Ця стаття демонструє перевірку даних
http://blogs.msdn.com/wpfsdk/archive/2007/10/02/data-validation-in-3-5.aspx

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

Більшу частину часу ви можете взяти об’єкт (модель), створений деяким ORM, і загортати його у ViewModel, який містить його, і команди для збереження / видалення - і ви готові прив’язати інтерфейс користувача безпосередньо до властивостей моделі.

Вид буде виглядати приблизно так (ViewModel має властивість, Itemщо містить модель, як клас, створений в ORM):

<StackPanel>
   <StackPanel DataContext=Item>
      <TextBox Text="{Binding FirstName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
      <TextBox Text="{Binding LastName, Mode=TwoWay, ValidatesOnDataErrors=True}" />
   </StackPanel>
   <Button Command="{Binding SaveCommand}" />
   <Button Command="{Binding CancelCommand}" />
</StackPanel>

Діалоги: Діалоги та MVVM трохи хитрі. Я вважаю за краще використовувати аромат підходу Mediator з діалогами, ви можете прочитати трохи більше про це в цьому питанні StackOverflow:
Приклад діалогу WPF MVVM

Мій звичайний підхід, який не зовсім класичний MVVM, можна узагальнити так:

Базовий клас для діалогового вікна ViewModel, який виставляє команди для виконання чи скасування дій, подія, яка дає зрозуміти, що діалог готовий до закриття, і все, що вам знадобиться у всіх діалогах.

Універсальний вигляд діалогового вікна - це може бути вікно або спеціальне "модальне" управління накладним типом. В його основі лежить контент-презентатор, в який ми скидаємо модуль перегляду, і він обробляє проводку для закриття вікна - наприклад, при зміні контексту даних ви можете перевірити, чи нове ViewModel успадковане від вашого базового класу, і якщо він є, підпишіться на відповідну подію закриття (обробник призначить результат діалогу). Якщо ви надаєте альтернативну функцію універсального закриття (наприклад, кнопка X), переконайтеся, що також запустіть відповідну команду закриття на ViewModel.

Десь вам потрібно надати шаблони даних для своїх ViewModels, вони можуть бути дуже простими, тим більше, що ви, мабуть, маєте представлення для кожного діалогового вікна, інкапсульованого в окремому елементі управління. Шаблон даних за замовчуванням для ViewModel виглядатиме приблизно так:

<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
   <views:AddressEditView DataContext="{Binding}" />
</DataTemplate>

Діалоговий вигляд повинен мати доступ до них, оскільки в іншому випадку він не знатиме, як показати ViewModel, окрім інтерфейсу спільного діалогу, його вміст в основному такий:

<ContentControl Content="{Binding}" />

Неявний шаблон даних буде відображати перегляд моделі, але хто її запускає?

Це не дуже mvvm частина. Один із способів зробити це - використовувати глобальну подію. Що я вважаю, що краще зробити, це використовувати налаштування типу агрегатора подій, що забезпечується за допомогою введення залежності - таким чином подія є глобальною для контейнера, а не для всього додатка. Призма використовує рамки єдності для контейнерної семантики та введення залежностей, і загалом мені дуже подобається Unity.

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

Налаштування цього способу дозволяє ViewModels просити програму відкрити діалогове вікно та відповісти на дії користувача, не знаючи нічого про інтерфейс користувача, тому здебільшого MVVM-ness залишається завершеним.

Однак бувають випадки, коли інтерфейс повинен піднімати діалоги, що може зробити щось складніше. Розглянемо, наприклад, якщо положення діалогу залежить від розташування кнопки, яка її відкриває. У цьому випадку вам потрібно мати певну інформацію про інтерфейс користувача, коли ви вимагаєте відкрити діалогове вікно. Як правило, я створюю окремий клас, який містить ViewModel та деяку відповідну інформацію про інтерфейс користувача. На жаль, деяка зв'язок там видається неминучою.

Псевдо-код обробника кнопок, який відкриває діалогове вікно, яке потребує даних про положення елементів:

ButtonClickHandler(sender, args){
    var vm = DataContext as ISomeDialogProvider; // check for null
    var ui_vm = new ViewModelContainer();
    // assign margin, width, or anything else that your custom dialog might require
    ...
    ui_vm.ViewModel = vm.SomeDialogViewModel; // or .GetSomeDialogViewModel()
    // raise the dialog show event
}

Діалогове вікно буде прив’язане до даних про позицію та передасть міститься ViewModel до внутрішнього ContentControl. Сам ViewModel досі нічого не знає про інтерфейс користувача.

Як правило, я не використовую DialogResultвластивість return ShowDialog()методу або очікую, що нитка блокується, поки діалогове вікно не закриється. Нестандартний модальний діалог не завжди працює так, і в складеному середовищі ви часто не хочете, щоб обробник подій як-небудь блокувався. Я вважаю за краще дозволити ViewModels займатися цим - творець ViewModel може підписатися на його відповідні події, встановити методи фіксації / скасування тощо, тому не потрібно покладатися на цей механізм інтерфейсу користувача.

Тож замість цього потоку:

// in code behind
var result = somedialog.ShowDialog();
if (result == ...

Я використовую:

// in view model
var vm = new SomeDialogViewModel(); // child view model
vm.CommitAction = delegate { this.DoSomething(vm); } // what happens on commit 
vm.CancelAction = delegate { this.DoNothing(vm); } // what happens on cancel/close (optional)
// raise dialog request event on the container

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


Дякуємо за детальну відповідь! Нещодавно я виявив, що моя найбільша проблема полягає в тому, коли мені потрібно мати MainViewModel, щоб спілкуватися з іншими моделями перегляду, щоб обробляти потік програми. Однак видається, що MVVM + Mediator здається популярним підходом.
jwarzech

2
Медіатор, безумовно, допомагає, модель агрегування подій (призма має гарну реалізацію) також дуже корисна, коли мета - низьке з'єднання. Крім того, ваша основна модель перегляду зазвичай має власні дочірні моделі, і вона не повинна мати проблем із спілкуванням з ними. Вам потрібно використовувати посередник або / і агрегатор подій, коли ваші дочірні моделі перегляду повинні взаємодіяти з іншими модулями у вашому додатку, про які вони не обов’язково знають - включаючи інтерфейс користувача (мій приклад діалогу стосується конкретного випадку).
Єгор

1
Вказівки по роботі з діалогами та вікнами були дуже корисними. Однак я застряг у кількох питаннях: 1. Як встановити назву вікна з подання? 2. Як ви маєте справу з налаштуванням вікна власника?
djskinner

@Daniel Skinner: Я припускаю, що ви тут говорите про діалоги, виправте мене, якщо я помиляюся. Заголовок діалогу - це ще одна властивість, і ви можете прив’язати його до всього, що вам подобається. Якщо ви дотримувались мого підходу із базовим діалоговим класом viewmodel (давайте зробимо вигляд, що він має властивість заголовка), то у вашому загальному діалоговому вікні ви можете використовувати прив'язку інтерфейсу до інтерфейсу для встановлення заголовка на {Binding Path = DataContext.Title, ElementName = NameOfContentPresenter}. Вікно власника трохи хитріше - це означає, що посередник, який насправді вискакує діалогове вікно, повинен знати про вигляд кореневої програми.
Єгор

Насправді я повертаю це назад - незалежно від того, як ви структуруєте це в якийсь момент, тому, хто насправді вискакує діалогове вікно, повинно мати посилання на кореневе вікно / вид програми. Зверніть увагу, де я сказав: "Зазвичай, кореневе вікно має сенс підписатись на цю подію - воно може відкрити діалогове вікно і встановити свій контекст даних для перегляду, який передається разом із підвищеною подією". Тут ви б встановили власника.
Єгор

6

Джейсон Долінгер зробив хорошу екранізацію MVVM. Як згадував Єгор, немає жодного хорошого прикладу. Вони повсюди. Більшість є хорошими прикладами MVVM, але не тоді, коли ви стикаєтесь із складними проблемами. У кожного свій шлях. Лоран Буньйон має хороший спосіб спілкування і між моделями перегляду. http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx Cinch також є хорошим прикладом. У Пола Стовеля є хороший пост, який дуже багато пояснює і його рамки Магеллана.


3

Ви дивилися на Калібурн ? Зразок ContactManager містить у собі багато хороших речей. Загальні зразки WPF також забезпечують хороший огляд команд. Документація досить хороша, а форуми активні. Рекомендовано!





2

Я також поділився вашими розчаруваннями. Я пишу заявку, і у мене були такі 3 вимоги:

  • Розширюваний
  • WPF з МВВМ
  • Приклади сумісних з GPL

Все, що я знайшов, були шматочки та шматки, тож я просто почав писати це найкраще, що міг. Коли я трохи поглибився, я зрозумів, що можуть бути інші люди (як ти сам), які можуть використовувати довідковий додаток, тому я переробив узагальнені речі в рамки програм WPF / MVVM і випустив її під LGPL. Я назвав його SoapBox Core . Якщо ви перейдете на сторінку завантажень, ви побачите, що вона постачається з невеликою демонстраційною програмою, а вихідний код цієї демо-програми також доступний для завантаження. Сподіваюсь, ви вважаєте це корисним. Крім того, напишіть мені на електронну адресу scott {at} soapboxautomation.com, якщо ви хочете отримати додаткову інформацію.

EDIT : Також розміщено статтю CodeProject, в якій пояснюється, як вона працює.


2

Я написав простий приклад MVVM з нуля на проекті коду, ось посилання MVVM WPF покроково . Він починається з простої тришарової архітектури і закінчує використовувати якийсь фреймворк, наприклад PRISM.

введіть тут опис зображення


1

Навіть я поділився розчаруванням, поки не взяв справу в свої руки. Я запустив IncEditor.

IncEditor ( http://inceditor.codeplex.com ) - це редактор, який намагається познайомити розробників з WPF, MVVM та MEF. Я почав це і встиг отримати певну функціональність на зразок підтримки "теми". Я не є експертом у галузі WPF чи MVVM чи MEF, тому не можу вкласти у неї багато функціональних можливостей. Я щиро прошу вас, хлопці, зробити це краще, щоб годівниці, як я, могли краще зрозуміти це.

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