Ответ 1
К сожалению, нет ни одного замечательного приложения 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 немного сложны. Я предпочитаю использовать аромат подхода "Посредник" с диалоговыми окнами, вы можете прочитать немного больше об этом в этом вопросе StackOverflow:
Пример диалога WPF MVVM
Мой обычный подход, который не совсем классический MVVM, можно резюмировать следующим образом:
Базовый класс для диалога ViewModel, который предоставляет команды для фиксации и отмены действий, событие, позволяющее представлению знать, что диалог готов к закрытию, и все, что вам понадобится во всех ваших диалогах.
Общий вид для вашего диалога - это может быть окно или настраиваемый тип "модальный" тип наложения. По сути, это презентатор контента, который мы даем в viewmodel и обрабатываем проводку для закрытия окна - например, при изменении контекста данных вы можете проверить, унаследована ли новая ViewModel из вашего базового класса, и если это так, подписаться на соответствующее событие закрытия (обработчик назначит результат диалога). Если вы предоставляете альтернативную универсальную функциональность (например, кнопка X), вы должны также запустить соответствующую команду закрытия в ViewModel.
Где-то вам нужно предоставить шаблоны данных для ваших ViewModels, они могут быть очень простыми, особенно, поскольку у вас, вероятно, есть представление для каждого диалога, инкапсулированного в отдельный элемент управления. Шаблон данных по умолчанию для ViewModel будет выглядеть примерно так:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}>
<views:AddressEditView DataContext={Binding} />
</DataTemplate>
В диалоговом представлении должен быть доступ к ним, поскольку в противном случае он не будет знать, как показать ViewModel, кроме общего диалогового UI, его содержимое в основном таково:
<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 все еще ничего не знает о пользовательском интерфейсе.
В общем, я не использую свойство return DialogResult
метода 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
Я предпочитаю это так, потому что большинство моих диалогов - это неблокирующие псевдомодальные элементы управления, и делать это таким образом кажется более простым, чем работа над ним. Легко unit test.