Хорошие примеры шаблона MVVM

144

В настоящее время я работаю с шаблоном Microsoft MVVM и считаю, что отсутствие подробных примеров меня разочаровывает. Включенный пример ContactBook показывает очень мало обработки команд, и единственный другой пример, который я нашел, взят из статьи MSDN Magazine, где концепции похожи, но использует немного другой подход и по-прежнему не имеет какой-либо сложности. Есть ли какие-нибудь достойные примеры MVVM, которые хотя бы показывают основные операции CRUD и переключение диалога / контента?


Все предложения были действительно полезны, и я начну составлять список хороших ресурсов.

Фреймворки / шаблоны

Полезные статьи

Скринкасты

Дополнительные библиотеки

Jwarzech
источник
Я рад, что эти ресурсы помогли. В настоящее время я работаю над своим вторым производственным приложением MVVM и буду продолжать добавлять контент, который будет полезен для тех, кто только начинает с ним сталкиваться.
jwarzech

Ответы:

59

К сожалению, нет ни одного отличного примера приложения 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

Я предпочитаю этот способ, потому что большинство моих диалогов - это неблокирующие псевдомодальные элементы управления, и сделать это таким способом кажется более простым, чем обходиться без него. Также легко провести модульное тестирование.

Егор
источник
Спасибо за подробный ответ! Недавно я обнаружил, что моя самая большая проблема - это когда мне нужно, чтобы MainViewModel взаимодействовал с другими моделями представления для обработки потока приложения. Однако похоже, что MVVM + Mediator - популярный подход.
jwarzech
2
Посредник определенно помогает, шаблон агрегатора событий (у Prism хорошая реализация) также очень полезен, когда целью является низкая связь. Кроме того, ваша основная модель просмотра обычно имеет собственные дочерние модели просмотра, и у нее не должно быть проблем с взаимодействием с ними. Вам необходимо использовать посредник или / или агрегатор событий, когда вашим дочерним моделям представления необходимо взаимодействовать с другими модулями в вашем приложении, о которых они не обязательно знают, включая пользовательский интерфейс (мой пример диалога касается этого конкретного случая).
Егор
1
Рекомендации по работе с диалогами и окнами были действительно полезны. Однако я застрял с несколькими проблемами: 1. Как установить заголовок окна из представления? 2. Как вы относитесь к настройке окна владельца?
djskinner
@ Дэниел Скиннер: Полагаю, вы говорите здесь о диалогах, поправьте меня, если я ошибаюсь. Заголовок диалога - это просто еще одно свойство, и вы можете привязать его к чему угодно. Если вы следовали моему подходу с базовым классом модели представления диалогового окна (давайте представим, что у него есть свойство заголовка), то во всем общем диалоговом окне вы можете использовать привязку пользовательского интерфейса к пользовательскому интерфейсу, чтобы установить заголовок на {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 также является хорошим примером. У Пола Стовела есть хороший пост, который тоже многое объясняет с его фреймворком Magellan.

nportelli
источник
3

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

Энди С
источник
3

Здесь я добавляю ссылку на приложение WPF (Inventory Management App), использующее архитектуру MVVM, разработанную мной.

Его интерфейс потрясающий. https://github.com/shivam01990/InventoryManagement

Шивам Шривастава
источник
2

В примере проекта в среде Cinch показаны основные инструменты CRUD и навигации. Это довольно хороший пример использования MVVM и включает в себя статью, состоящую из нескольких частей, объясняющую его использование и мотивацию.

Рид Копси
источник
2

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

  • Расширяемый
  • WPF с MVVM
  • Примеры, совместимые с GPL

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

РЕДАКТИРОВАТЬ : Также опубликована статья CodeProject, объясняющая, как это работает.

Скотт Уитлок
источник
2

Я написал простой пример MVVM с нуля в проекте кода, вот ссылка MVVM WPF шаг за шагом . Он начинается с простой трехуровневой архитектуры и дает вам возможность использовать некую структуру, такую ​​как PRISM.

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

Шивпрасад Коирала
источник
1

Даже я разделял разочарование, пока не взял дело в свои руки. Я запустил IncEditor.

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

Абдулсаттар Мохаммед
источник