Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Разработка графического пользовательского интерфейса в соответствии с паттерном Model-View-Viewmodel на платформе Windows Presentation Foundation. Осн

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
1 Мб
Скачать
☆

Рис. 3. Модель представления как адаптер (посредник)

Основная идея паттерна заключается в том, что создается так называемый контекст данных, т.е. динамически обновляемая и автоматически синхронизируемая область данных. Любой модуль, который подключен к контексту данных, всегда «видит» актуальные значения его элементов. Контекст также позволяет изменять данные, доступные для записи соответствующим подключенным блокам, при этом автоматически будут обновлены все компоненты, зависимые от этих изменений. Это позволяет «подключать» визуальные элементы GUI к контексту данных так же просто, как подключаются электрические кабельные соединения, достаточно только задать соответствие «контактов».

11

Связывание данных

Платформа должна предоставлять программную реализацию контекста данных, точнее механизм «подключения», связывания разных компонентов посредством синхронизируемых полей данных. На платформе WPF для этого используется технология Data Binding (связывание данных). Подробности содержатся в соответствующей документации, а в настоящем пособии приведем основные ее концепции и возможности.

Data Binding рассматривает для каждого поля данных, участвующего в связывании, две стороны: источник (source) и приемник (target). Связывание характеризуется направлением, в котором при прозрачной поддержке платформы осуществляется автоматическая синхронизация данных. При однонаправленном связывании данные автоматически переносятся только из источника в приемник. При двунаправленном связывании инициатором обновления может служить любая сторона. Существуют и другие схемы обновления, но они находят применение существенно реже.

Тип данных, участвующих в связывании, может быть любым. Ограничения накладываются не на типы, а на способ хранения самих значений. Обычная переменная не может участвовать в Data Binding. В самом деле, обычная переменная – это, по сути, участок памяти, доступ к которому почти не контролируется, так что невозможно выполнить автоматическое обновление всех связанных с этой ячейкой компонентов. Более реалистичным способом является использование функций вида GetValue/SetValue, которые следует вызывать всякий раз при обращении к соответствующей переменной на чтение или запись.

В .NET принято оформлять такие данные в виде свойств объектов (properties), поскольку для свойств предусмотрены стандартные операции get и set, позволяющие синтаксически обращаться к полю данных

12

как к обычной переменной (например, Width = 100; Height = Width/2). При этом будут вызываться алгоритмические действия, заданные в описании операций get (при чтении значения) и set (при записи значения). Таким образом, для участия данных в Data Binding их обязательно оформляют в виде свойств каких-либо объектов. Однако было бы крайне неудобно в каждой операции get или set описывать процедуру извлечения данных из связанных ячеек или распространения волны обновлений. Логично поручить это инфраструктуре платформы и нагрузить операции get и set только вызовами методов GetValue/SetValue, определенными для соответствующего системного компонента, причем их желательно унаследовать. В WPF такой вспомо-

гательный класс называется DependencyObject.

Кроме того, нужно каким-то образом идентифицировать переменные, чтобы передавать эти идентификаторы методам GetValue/SetValue, унаследованным от DependencyObject. Анализ показывает, что для полноценной поддержки Data Binding требуются не только идентификаторы ячеек, выделенных для хранения данных, но и дополнительные программные элементы, например callback-функции, вызываемые при изменении значений. Следовательно, свойство, участвующее в Data Binding, характеризуется не просто именем и типом, но и совокупностью ассоциированных друг с другом полей, образующих объект. При создании этого объекта (в WPF его класс называется DependencyProperty) могут быть указаны callback-функция оповещения, исходное значение и некоторые другие метаданные, т.е. данные о самом свойстве (property metadata). Ссылка на объект DependencyProperty передается в методы GetValue/SetValue. А поскольку метаданные любых свойств классанаследника DependencyObject относятся не к экземпляру, а к самому типу, то описатель свойства должен быть статическим членом класса.

Приведем пример описания булева (Boolean) свойства с именем IsEnabled, являющегося членом класса Example:

13

public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register

(

“IsEnabled”, // имя свойства typeof(Boolean), // тип свойства typeof(Example), // тип класса-владельца new PropertyMetadata

(

true, // исходное значение

OnEnabledChanged // callback-функция оповещения

)

);

public Boolean IsEnabled

{

get { return (Boolean)GetValue(IsEnabledProperty); } set { SetValue(IsEnabledProperty, value); }

}

Как видно, синтаксис довольно громоздкий, причем в примере описан далеко не полный вариант. Вероятно, должны быть веские причины и существенные «выгоды» использования подобных свойств. И положительные стороны этой технологии, действительно, весьма полезны. Любое DependencyProperty способно участвовать в Data Binding. Можно считать, что оно и является тем «слотом», через который данные модели предметной области «подключаются» к отображающим их элементам GUI. Визуальный дизайнер получает возможность без написания какого-либо программного кода указывать каналы обмена данными GUI с моделью, просто выбирая их из списка доступных Dependencyсвойств. Каждый такой канал будет автоматически поставлять данные модели элементам GUI и при необходимости передавать обратно их изменения, осуществленные пользователем в процессе ввода. Какому классу принадлежат «связываемые» свойства, как их описание попадает в визуальный редактор системы программирования и что представляют из себя «соединительные кабели» – это отдельные вопросы, важные для понимания паттерна MVVM. Они будут рассмотрены далее.

14

Событийно-ориентированный ввод

В событийно-ориентированном GUI присутствуют сигналы от устройств ввода – события (events), происходящие во время манипуляций пользователя над визуальными элементами или инициируемые посредством клавиатуры. Возникающие события, как правило, ассоциированы с участками экрана. Так, например, при щелчке мышью событие «Щелчок» (Click) логично соотносить с той областью, над которой в данный момент находится курсор мыши. Программист, описывающий алгоритм обработки события, должен знать смысл, заложенный в соответствующее действие пользователя, иначе программе невозможно адекватно отреагировать на него.

Различных типов событий (мышиные, клавиатурные и другие, относящиеся к манипуляциям посредством устройств ввода) относительно немного. Так, мышь можно перемещать, на ней можно нажимать клавиши, можно перемещать мышь с нажатыми клавишами, можно выполнять кратные щелчки (двойной), можно крутить на мыши колесо, если оно имеется, – едва ли список действий с этим привычным манипулятором можно существенно расширить. Но смысл, вкладываемый пользователем в любые манипуляции, зависит как от состояния программы, так и от состояния устройств ввода (координаты указателя, состояние клавиш, и др.), поэтому множество «смыслов манипуляций» почти бесконечно.

Сам по себе этот факт не является проблемным. Ведь проектируются самые разнообразные программы с самыми разнообразными функциями и поведением. Проблемы начинаются в том случае, когда программист, создающий алгоритмы обработки событий, не имеет информации об элементах GUI, с которыми события соотнесены. А не имеет он этой информации по той причине, что дизайнер мог еще даже не решить, из каких именно визуальных элементов будет состоять разра-

15

батываемый пользовательский интерфейс. Трудно или невозможно «вслепую» обеспечивать адекватное поведение программы.

Позволить дизайнеру и программисту работать над проектом параллельно – такой сценарий в указанных условиях кажется почти недостижимым. Тем не менее большинство проблем с изоляцией алгоритмов обработки событий пользовательского ввода от конкретных источников возникновения этих событий можно решить при поддержке со стороны платформы. Для этого WPF предлагает следующие средства: команды (commands), маршрутизируемые события (routed events), систему автоматического размещения визуальных элементов (layout system).

Понятие команда является смысловым обобщением события. Например, событие «Щелчок на кнопке открытия файла» может обобщаться командой «Открыть файл». Тогда соответствующие действия по открытию файла могут быть вызваны и любым другим способом – из меню, нажатием клавиатурной комбинации, выбором файла из списка и т.д. При необходимости команды могут получать параметры.

Программист выполняет реализацию смысловой логики команд, не имея информации о том, как они будут вызваны из GUI, а дизайнер должен иметь возможность «подключать» те или иные элементы GUI к командам. Таким образом, команды являются своего рода «слотами» управляющих сигналов, аналогично тому, как связываемые свойства являются «слотами» данных.

Пользовательские команды обычно генерируются элементами типа кнопок, пунктов меню или гиперссылок. Но события от устройств ввода этим не исчерпываются. Важную роль играют действия с помощью устройств позиционирования и клавиатуры. Поэтому WPF использует концепцию жеста (gesture), который может связывать команду с некоторым состоянием устройств ввода (например, команда будет вызвана при нажатии правой кнопки мыши с одновременным удерживанием

16

клавиши Ctrl). Жесты ввода могут быть соотнесены с любым элементом

GUI.

Одним из распространенных пользовательских действий является изменение размеров окон или каких-либо других областей, заполненных визуальным содержимым. Реакция на это действие должна заключаться в том, что дочерние элементы окна тоже изменяют свои размеры и/или перепозиционируются в соответствии с некоторой логикой. Очевидно, что программная реализация этой логики требует знания конкретных элементов GUI, порядка их расположения, их визуальных и смысловых свойств.

Эта проблема гораздо серьезнее, чем рассмотренная выше поддержка действий пользователя, сводимых к обобщенным командам и жестам. Логику размещения элементов уже нельзя изолировать от конкретного состава GUI, определяемого дизайнером. Но WPF и здесь предлагает решение, причем подходит к нему с неожиданной стороны: платформа вообще освобождает программиста от необходимости писать алгоритмы управления размерами и позициями элементов. Платформа берет на себя выполнение всех этих действий, а их декларативная настройка возлагается на дизайнера.

Такая технология называется системой размещения (layout system). Она оперирует не абсолютными координатами элементов, а относительными – в пределах объемлющего контейнера. Причем разметка задается не числовыми значениями, а характеристиками с более общим смыслом, например: «по центру контейнера», «во всю ширину контей-

нера». Поэтому настройка разметки приобретает качественный, а не количественный характер, что освобождает дизайнера от необходимости описания каких-либо вычислений на алгоритмическом языке. Подробности WPF Layout System приведены в соответствующей документации, а пока можно сделать вывод о том, что система размещения поз-

17

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

Обобщенные команды и сигналы к изменению разметки являются вторичными по отношению к низкоуровневым сообщениям пользовательского ввода, которые в Windows адресуются окнам – контролируемым операционной системой объектам. Системные сообщения всегда передаются в очередь (message queue) какого-либо окна, идентифицируемого уникальным дескриптором HWND (а окном в абстрактном смысле могут являться и кнопки, и строки ввода текста, и элементы прокрутки, и вообще самые разнообразные виджеты). Без информации о дескрипторе окна невозможно даже получить сообщения (так как неизвестно, из какой очереди их следует извлекать). WPF, так или иначе, работает поверх системного механизма ввода, поэтому сказанное справедливо и для всех создаваемых в WPF-приложении окон.

WPF предоставляет дизайнеру GUI набор управляющих элементов пользовательского интерфейса (controls). Все они пользуются инфраструктурой платформы, обеспечивающей преобразование низкоуровневых сообщений Windows в команды, жесты, действия перепозиционирования. Фактически сначала происходит преобразование Windowsсообщений в специальные маршрутизируемые события (routed events), а уже на их основе генерируются обобщенные сигналы типа команд.

Термин «маршрутизация событий» означает, что информация о событиях перемещается по дереву визуальных элементов GUI. Если Windows-события диспетчеризуются, т.е. поступают только в очередь одного окна – целевого, то в отличие от них маршрутизируемые события последовательно проходят путь между целевым визуальным элементом и контейнером самого высшего уровня (окном приложения).

Такая стратегия обеспечивает возможность иерархической компоновки элементов GUI из нескольких составляющих. Независимо от того, из каких частей состоит элемент, все они объединяются в дерево, по ко-

18

торому проходят маршрутизируемые события, так что контейнер верхнего уровня имеет возможность отслеживать события, возникающие ниже. Таким образом, дизайнер практически не ограничен в способах объединения базовых элементов в сложные конструкции для достижения требуемых декоративных или эргономических целей. Однако команды и жесты могут связываться как с отдельными частями, так и со всей иерархической конструкцией в целом – WPF автоматически «поднимает» события, если они не обработаны на нижних уровнях. Для программиста это означает дополнительную независимость от фактического состава GUI и возможность получения управляющих сигналов от некоторых обобщенных «контейнеров», «блоков», «страниц», «регионов».

19

XAML в WPF

Утверждение о том, что при работе над GUI дизайнер должен быть освобожден от необходимости пользоваться языком программирования, следует уточнить. Во-первых, дизайнер не должен описывать какиелибо алгоритмы в императивном стиле (в виде последовательности инструкций для исполнения). Во-вторых, задача дизайнера – структурная компоновка и визуальное оформление GUI с использованием предопределенных объектов и инструментов, а не создание каких-либо новых моделей взаимодействия программных компонентов. Дизайнер фактически конструирует метаданные, а не программные модули. Такая работа является описательной, декларативной. Следовательно, дизайнер в своей работе может пользоваться декларативным языком, описывающим структурные взаимоотношения между различными сущностями в программе.

Язык XAML (Extensible Application Markup Language, расширяемый язык разметки приложений) является основным языком разметки GUI на платформе WPF. Это декларативный предметно-ориентированный расширяемый язык, основанный на XML. Во многих случаях при хорошей поддержке со стороны специализированного текстового редактора верстка на XAML даже удобнее визуального редактирования.

Вот пример описания на XAML некоторого фрагмента GUI:

<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">

<Button Width="100" Height="40" Margin="10" HorizontalContentAlignment="Center">

Yes </Button>

<Button Width="100" Height="40" Margin="10" HorizontalContentAlignment="Center">

No </Button>

</StackPanel>

Этот фрагмент создает следующее визуальное представление:

20

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]