К сожалению, нет ни одного отличного примера приложения MVVM, которое бы все делало, и есть много разных подходов к этому. Во-первых, вы можете ознакомиться с одной из существующих платформ приложений (Prism - достойный выбор), потому что они предоставляют вам удобные инструменты, такие как внедрение зависимостей, команды, агрегация событий и т. Д., Чтобы легко опробовать различные подходящие вам шаблоны. .
Релиз призмы:
http://www.codeplex.com/CompositeWPF
Он включает в себя довольно приличный пример приложения (биржевой трейдер) вместе с множеством небольших примеров и инструкций. По крайней мере, это хорошая демонстрация нескольких общих подшаблонов, которые люди используют, чтобы заставить MVVM действительно работать. Думаю, у них есть примеры как для CRUD, так и для диалогов.
Prism не обязательно подходит для каждого проекта, но с ней хорошо познакомиться.
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 немного сложны. Я предпочитаю использовать разновидность подхода Посредника с диалогами, вы можете прочитать немного больше об этом в этом вопросе StackOverflow:
Пример диалога WPF MVVM
Мой обычный подход, не совсем классический MVVM, можно резюмировать следующим образом:
Базовый класс для диалоговой модели ViewModel, который предоставляет команды для действий фиксации и отмены, событие, позволяющее представлению узнать, что диалоговое окно готово к закрытию, и все остальное, что вам понадобится во всех ваших диалогах.
Общее представление для вашего диалога - это может быть окно или настраиваемый элемент управления типа «модальное» наложение. По сути, это презентатор контента, в который мы выгружаем модель просмотра, и он обрабатывает проводку для закрытия окна - например, при изменении контекста данных вы можете проверить, унаследована ли новая модель просмотра от вашего базового класса, и если это так, подписаться на соответствующее событие закрытия (обработчик назначит результат диалога). Если вы предоставляете альтернативную универсальную функцию закрытия (например, кнопку X), вы также должны запустить соответствующую команду закрытия в ViewModel.
Где-то вам нужно предоставить шаблоны данных для ваших ViewModels, они могут быть очень простыми, особенно потому, что у вас, вероятно, есть представление для каждого диалогового окна, инкапсулированного в отдельный элемент управления. Тогда шаблон данных по умолчанию для ViewModel будет выглядеть примерно так:
<DataTemplate DataType="{x:Type vmodels:AddressEditViewModel}">
<views:AddressEditView DataContext="{Binding}" />
</DataTemplate>
Диалоговое представление должно иметь доступ к ним, потому что в противном случае оно не будет знать, как отображать ViewModel, за исключением пользовательского интерфейса общего диалогового окна, его содержимое в основном следующее:
<ContentControl Content="{Binding}" />
Шаблон неявных данных отобразит представление на модель, но кто его запускает?
Это не совсем mvvm часть. Один из способов сделать это - использовать глобальное событие. Я думаю, что лучше всего использовать настройку типа агрегатора событий, предоставляемую посредством внедрения зависимостей - таким образом, событие является глобальным для контейнера, а не для всего приложения. 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
свойство return ShowDialog()
метода и не ожидаю, что поток заблокируется, пока диалоговое окно не будет закрыто. Нестандартный модальный диалог не всегда работает подобным образом, а в составной среде вы часто не хотите, чтобы обработчик событий блокировал подобное. Я предпочитаю, чтобы ViewModel занимался этим - создатель 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
Я предпочитаю этот способ, потому что большинство моих диалогов - это неблокирующие псевдомодальные элементы управления, и сделать это таким способом кажется более простым, чем обходиться без него. Также легко провести модульное тестирование.