WPF, Паттерн MVVM

MVVM (Model-View-ViewModel) - це шаблон, який з'явився для обходу обмежень патернів MVC і MVP, і поєднує деякі з їхніх сильних сторін. Ця модель вперше з'явилася у складі фреймворку Small Talk у 80-х, і була пізніше покращена з урахуванням оновленої моделі презентацій (MVP).

На наступному малюнку представлено діаграму, яка показує, як реалізувати шаблон MVVM. Звичайно, це загальна реалізація:

прив

Якщо ви плануєте працювати з WPF або Silverlight, ви повинні скористатися механізмом прив'язок (binding), що надається цими технологіями. Для цього, ваша модель-подання має реалізувати деякі конкретні інтерфейси, необхідні модулю прив'язки WPF і Silverlight. Одним з них єINotifyPropertyChanged, введений у .NET Framework, починаючи з версії 2 і вище. Цей інтерфейс реалізує систему повідомлень, яка активується, коли значення властивості змінюється. Це потрібно в моделі-уявлення, щоб зробити механізм прив'язки інтерфейсу користувача XAML динамічним.

Іншим налаштуванням є команди, що надаються інтерфейсом ICommand, які доступні для WPF та Silverlight. Команди можуть прив'язуватися до певного XAML-елементу та визначати поведінку даного елемента за певних дій.

Третім компонентом у наведеній вище структурі є шаблон даних DataTemplate, який визначає як структурувати конкретну модель-подання або особливий стан моделі-подання.

Приклад програми MVVM

прив

Як бачите уявлення тепер порожнє (містить тільки інтерфейс користувача на XAML), вся логіка будується на відносинах між моделлю і модель-поданням.

Структура View Models

Нам знадобиться два види моделей для цієї програми. Кожна модель-вистави має свої власні завдання у поданні. Використання кількох вкладених моделей називається ієрархічним модель-поданням (hierarchical view model).

Модель-подання ProjectsViewModel у нашому додатку буде містити стан і логіку представлення ProjectsView, це показано на наступному малюнку:

паттерн

Властивості прив'язки та їх призначення описані нижче:

Projects - представляє оновлювану колекцію ObservableCollection

для прив'язки до елемента ComboBox у поданні.

SelectedValue - ціла чисельність, прив'язана до обраного проекту в ComboBox з використанням односторонньої прив'язки. Ця властивість допомагає отримувати сповіщення про зміну вибраного елемента в ComboBox.

SelectedProject - властивість, що зв'язує деталі подання (кошторисна вартість, фактична вартість) з типом проекту.

DetailsEnabled - прив'язується до властивості IsEnabled текстових полів кошторисної та фактичної вартості у поданні. Якщо ComboBox проект не вибрано, поля блокуються.

DetailsEstimateStatus - перерахування класу Status, яке прив'язується в тригерах, що керують кольором текстового поля фактичної вартості.

UpdateCommand – прототип інтерфейсу ICommand пов'язаний з кнопкою UpdateButton.

Друга модель-подання ProjectViewModel міститиме стан подання та логіку для управління деталями у нашому поданні (фактичну та кошторисну вартості):

паттерн

Структура цього модель-вистави дуже схожа на базовий клас Project тому що воно реалізує інтерфейс IProject. (Нагадаю, якщо ви ще цього не зробили, необхідно додати до проекту посилання на складанняProjectBilling.DataAccess.dll ми створили в темі RAD & Monolitic і код доступу до даних - базовий клас Project, інтерфейс IProject і кілька допоміжних методів для вилучення даних.

Модель у нашому додатку представлена ​​класом ProjectsModel, що реалізує інтерфейс IProjectsModel. Інтерфейс я додав для можливого розширення програми шляхом впровадження залежностей та полегшення тестування, він включає наступні члени:

Projects - містить колекцію проектів ObservableCollection

прив'язану до властивості Projects модель-подання ProjectsViewModel.

ProjectUpdated - подія оновлення даних проекту. Оновлення даних у моделі призводить до автоматичного оновлення всіх подань у додатку.

UpdateProject - фактично обробник події ProjectUpdated, що викликає метод Update() екземпляра Project.

Для початку нам потрібно визначити кілька допоміжних класів та інтерфейс IProjectsModel. Клас Notifier реалізує інтерфейс INotifyPropertyChanged і буде використовуватися в моделі та моделі-подання для повідомлень про зміни. Фактично він є оболонкою для інкапсуляції INotifypropertyChanged. Клас ProjectsEventArgs просто додає аргументи (як властивості) в обробник події IProjectsModel.ProjectUpdated (у нашому прикладі будемо передавати тільки посилання на прототип IProject). Додайте наступний код до файлу моделі, наприклад BillingProject.Model.cs:

Структура класу ProjectsModel виглядає так:

ProjectViewModel

Тепер додамо клас ProjectViewModel, що реалізує одну з двох модель-виставу:

Інтерфейс IProjectViewModel зобов'язує додати посилання на цей перелік. Сам клас ProjectViewModel досить простий, віноголошує кілька закритих змінних (інкапсульованих у загальнодоступних властивостях), які відповідають сигнатурі базового класу Project. ProjectViewModel успадковано від Notifier, тому в загальнодоступних властивостях включена підтримка повідомлень про зміну.

Додамо також у цей клас два конструктори та кілька допоміжних методів:

Тут ми додали конструктор, який передається прототип інтерфейсу IProject і в якому відбувається оновлення за рахунок виклику методу Update(), який легко оновлює ProjectViewModel і деталі проекту в поданні. Допоміжний метод UpdateEstimateStatus() реалізує додаткову логіку порівняння фактичної вартості проекту із кошторисною.

ProjectsViewModel

Насамперед оголосимо перерахування Status, яке містить три можливі значення: None - фактична вартість дорівнює нулю, Good - фактична вартість менша або дорівнює кошторисній вартості, Bad - фактична вартість відповідно перевищує кошторисну:

Тепер додамо інтерфейс IProjectsViewModel, що реалізує INotifyPropertyChanged, що містить посилання на обраний об'єкт моделі-подання IProjectViewModel і метод UpdateProject, що оновлює деталі представлення:

Тепер додамо сам клас модель-вистави ProjectsViewModel:

Отже, давайте розберемо цей код. ProjectsViewModel успадкований від Notifier, це додає підтримку інтерфейсу INotifyPropertyChanged (без його явної реалізації), включаючи сповіщення про зміни.

Змінна _model містить посилання на модель ProjectsModel, при цьому буде створено лише одну модель, загальну для всіх модель-уявлень. Колекція Projects містить посилання на IProjectsModel.Projects. Властивість SelectedValue прив'язується до номера обраногоелемента у ComboBox, тобто. ComboBox.SelectedIndex. При цьому в самій властивості оновлюється інша властивість SelectedProject і оновлюється статус через якість DetailsEstimateStatus.

UpdateCommand - властивість, що реалізує інтерфейс ICommand, що зв'язує подію кліка по кнопці Update у поданні з оновленням моделі.

У конструкторі класу ProjectsViewModel ініціалізується модель через змінну _model, приєднується обробник події IProjectsModel.ProjectUpdated і створюється екземпляр класу UpdateCommand, який буде показано трохи пізніше.

Метод UpdateProject() використовується у класі UpdateCommand для оновлення моделі. model_ProjectUpdated() - обробник подій, який буде викликатися у відповідь на подію IProjectsModel.ProjectUpdated. Цей обробник спочатку оновлює колекцію Projects, а потім змінюватиме властивість SelectedProject, якщо його ID такий самий, як і в оновленого проекту.

Давайте додамо клас UpdateCommand:

У конструкторі класу UpdateCommand ініціалізується змінна _vm, що містить посилання на IProjectsViewModel, і додається обробник події vm_PropertyChanged. Тут перевіряється джерело, яке викликало команду, IComand.CanExecuteChanged вкаже джерело команди і викличе ICommand.CanExecute().

CanExecute() - метод, реалізацію якого вимагає ICommand, викликається за командою від джерела, щоб визначити, чи може він виконати потрібну команду. Подання зазвичай відключає себе, коли ICommand.CanExecute() повертає false, а в нашому випадку це використовується, щоб контролювати, коли UpdateButton буде включено або вимкнено.

Подія CanExecuteChanged і метод Execute() також є частиною реалізації ICommand. У Execute() ми просто ініціалізуємо викликProjectsViewModel.UpdateProject.

Графічний інтерфейс

Тепер ми створимо графічний інтерфейс програми WPF, що використовує цю модель та модель-вистави. Ми будемо використовувати головне вікно програми (в моєму випадку MainWindow.xaml) як фабрику для запуску кількох додаткових вікон, які й реалізують уявлення. Додайте до проекту вікно ProjectsView.xaml:

Якщо ви тільки починаєте використовувати MVVM, вас може здивувати відсутність коду в частковому класі, що реалізує представлення. Насправді це наслідком використання MVVM, т.к. бізнес-логіку програми ми концентруємо в моделі, взаємодія уявлення та моделі в модель-уявленнях, а саме уявлення використовує прив'язки та команди за рахунок синтаксису XAML.

Розгляньмо структуру цього вікна докладніше, розбивши її на ключові елементи:

ComboBox – дозволяє користувачеві вибирати тип проекту. Властивість ItemsSource прив'язується до колекції ProjectsViewModel.Projects, при цьому DisplayMemberPath встановлюється в Name (тобто в списку, що розкривається, буде відображатися назва проекту), а SelectedValuePath в Id - виділений елемент повертає не сам об'єкт Project, а тільки його ідентифікатор.

SelectedValue прив'язується до однойменного властивості модель-подання ProjectsViewModel, у своїй зверніть увагу до тип прив'язки - OneWayToSource, тобто. використовується одностороння прив'язка, від ComboBox до SelectedValue.

Деталі подання реалізовані у вигляді двох текстових полів, що відображають кошторисну та фактичну вартість проекту. Ці деталі автоматично оновлюватимуться при виборі іншого типу проекту в ComboBox.

Кнопка Update використовує набір команд через прив'язку до ProjectsViewModel.UpdateCommand.

Звернітьувага, що у вікні також додано деяку логіку для подання, реалізованого в наборі тригерів стилю EstimateStyle. Умови тригерів прив'язуються до можливих значень переліку Status та задають різний колір шрифту в текстових полях, залежно від значень фактичної та кошторисної вартості.

Тепер додамо невелику логіку у вікні MainWindow.xaml для фабричного створення вікон ProjectsView:

Цей код безпосередньо не відноситься до реалізації MVVM, а просто слугує для допоміжних цілей.

Запустивши нашу програму і відкривши кілька вікон ProjectsView, виберіть в них однаковий проект, наприклад Jones, встановіть для нього фактичну вартість і натисніть кнопку Update. Ви побачите, що оновляться дані і в інших вікнах:

MVVM

Ця поведінка досягається тим, що оновлення одного уявлення викликає оновлення моделі, яка, у свою чергу, оновлює всі інші уявлення.

Переваги MVVM

Отже, розглянувши досить великий приклад програми MVVM, виникає резонне питання, а навіщо цей патерн взагалі використовувати, якщо того ж результату можна було досягти "малою кров'ю"? Я наведу кілька аргументів на користь MVVM:

Тестованість MVVM-додатків

Програми, розроблені з використанням MVVM, мають дуже хорошу основу для проведення модульного тестування з метою перевірки роботи окремих класів і методів.

Найменша кількість коду

Обсяг коду, необхідного для керування поданням, трохи знижується при використанні MVVM, а це означає, що знижується ризик допустити помилки і зменшується код для написання модульних тестів.

Поліпшене проектування додатків

Розробники та дизайнери можуть самостійнопрацювати над різними частинами програми. Як ви бачили на прикладі, уявлення генерується в XAML-розмітці і використовує базовий синтаксис прив'язок і команд для взаємодії з модель-поданням. Ви можете створити модель-виставу, яка надає необхідні точки входу для зв'язування з поданням (наприклад, загальнодоступні властивості), які в кінцевому поданні можна буде легко прив'язати. Це дозволяє дизайнерам працювати над зовнішнім виглядом програми, а програмістам над бізнес-логікою програми.

Легкість розуміння логіки уявлення

MVVM передбачає добре організовану та легку для розуміння конструкцію побудови графічного інтерфейсу за рахунок використання механізмів прив'язок, команд та шаблонів даних.