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