К сожалению, нет ни одного замечательного примера приложения MVVM, который бы делал все, и существует множество различных подходов к выполнению задач. Во-первых, вы можете ознакомиться с одной из существующих платформ приложений (Prism - достойный выбор), поскольку они предоставляют вам удобные инструменты, такие как внедрение зависимостей, команды, агрегация событий и т. Д., Чтобы легко опробовать различные шаблоны, которые вам подходят. ,
Выпуск призмы:
http://www.codeplex.com/CompositeWPF
Он включает в себя довольно приличное приложение с примерами (биржевой трейдер), а также множество небольших примеров и инструкции. По крайней мере, это хорошая демонстрация нескольких общих шаблонов, которые люди используют, чтобы заставить MVVM действительно работать. Я считаю, что у них есть примеры как для CRUD, так и для диалогов.
Призма не обязательно для каждого проекта, но с ней полезно ознакомиться.
CRUD:
Эта часть довольно проста: двусторонние привязки WPF позволяют легко редактировать большинство данных. Настоящий трюк состоит в том, чтобы предоставить модель, которая облегчает настройку пользовательского интерфейса. По крайней мере, вы хотите убедиться, что ваш ViewModel (или бизнес-объект) реализует INotifyPropertyChanged
для поддержки привязки, и вы можете привязывать свойства прямо к элементам управления пользовательского интерфейса, но вы также можете реализовать IDataErrorInfo
для проверки. Как правило, если вы используете какое-то решение ORM, настройка CRUD совсем несложна.
Эта статья демонстрирует простые операции 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}" />
Неявный шаблон данных сопоставит представление с моделью, но кто его запускает?
Это не такая уж и мввм часть. Один из способов сделать это - использовать глобальное событие. Я думаю, что лучше всего использовать настройку типа агрегатора событий, предоставляемую посредством внедрения зависимостей - таким образом, событие является глобальным для контейнера, а не для всего приложения. Prism использует инфраструктуру Unity для семантики контейнера и внедрения зависимостей, и в целом Unity мне очень нравится.
Обычно корневому окну имеет смысл подписаться на это событие - он может открыть диалоговое окно и установить свой контекст данных для ViewModel, который передается с вызванным событием.
Настройка этого таким образом позволяет ViewModels просить приложение открыть диалог и отвечать на действия пользователя там, ничего не зная об интерфейсе пользователя, поэтому по большей части MVVM-сущность остается завершенной.
Однако бывают случаи, когда пользовательский интерфейс должен вызывать диалоги, что может усложнить задачу. Рассмотрим, например, если позиция диалога зависит от расположения кнопки, которая ее открывает. В этом случае вам нужно иметь определенную информацию для пользовательского интерфейса при запросе открытия диалога. Я обычно создаю отдельный класс, который содержит 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
метода ShowDialog()
и не ожидаю, что поток будет блокироваться до закрытия диалога. Нестандартное модальное диалоговое окно не всегда работает таким образом, и в сложной среде вы часто не хотите, чтобы обработчик событий так или иначе блокировал это. Я предпочитаю позволить ViewModels справиться с этим - создатель ViewModel может подписаться на соответствующие события, установить методы commit / cancel и т. Д., Поэтому нет необходимости полагаться на этот механизм пользовательского интерфейса.
Итак, вместо этого потока:
// 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
Я предпочитаю это так, потому что большинство моих диалогов - это неблокирующие псевдомодальные элементы управления, и делать это таким образом кажется более простым, чем обходить его. Прост в модульном тестировании.