Технологии презентационного уровня приложений WPF
1. Сущность технологии WPF
Windows Presentation Foundation (WPF) – это графическая (презентационная) подсистема в составе .NET Framework (начиная с версии 3.0), имеющая прямое отношение к XAML.
WPF представляет собой высокоуровневый объектно- ориентированный функциональный слой (framework), позволяющий создавать 2D- и 3D-интерфейсы на базе DirectX. Ближайшими альтернативами можно назвать Adobe Flash и Adobe Air, но они не имеют многих возможностей WPF. Также существует урезанная версия CLR, известная как Silverlight.
Ниже приведены основные возможности технологии WPF:
- для отрисовки используется не GDI+, как в приложениях Windows Forms, а DirectX;
- WPF предоставляет не только интерфейс, но новейшие инструменты для связывания данных (data binding), а также другие сложные подсистемы для создания приложений;
- WPF сочетает использование как кода на .NET-языке, так и на XAML, компилирующегося в BAML-ресурсы. XAML представляет из себя XML, в котором фактически реализованы классы .NET Framework;
- реализована модель разделения кода и дизайна, позволяющая взаимодействовать программисту и дизайнеру;
- есть встроенная поддержка стилей элементов, а сами элементы легко разделить на элементы управления второго уровня, которые, в свою очередь разлагаются до уровня векторных фигур и свойств/действий. Это позволяет легко задать стиль для любого элемента, например, того же Button (кнопка).
- для работы с WPF требуется любой .NET-совместимый язык. В этот список входит много языков: C#, VB, C++, Ruby, Python, Delphi(Prism) и многие другие.
- производительность WPF выше, чем у GDI+ за счёт использования аппаратного ускорения графики через DirectX;
- для полноценной работы может быть использована как Visual Studio, так и Expression Blend. Впрочем, первая больше заточена под программирование, а вторая – на дизайн, и позволяет делать многие вещи, не прибегая к ручному редактированию XAML. Примеры этому – анимация, стилизация, состояния, создание эле- ментов управления и так далее.
Таким образом, с помощью WPF можно создавать широкий спектр как автономных, так и размещенных в браузере приложений.
2. Архитектура WPF
Основные компоненты архитектуры WPF приведены на рисунке 1.
Рисунок 1 – Архитектура WPF
Компонент PresentationFramework содержит типы WPF верхнего уровня, включая представление окна, панелей и других элементов управления. PresentationCore содержит базовые типы, такие как UIElement и Visual, от которых порождаются все формы и элементы управления. WindowsBase включает различные типы, которые могут использоваться за пределами WPF, в частности, компоненты DispatchObject и DependencyObject.
Компонент milcore является ядром визуализации WPF. WindowsCodecs представляет собой низкоуровневый API-интерфейс для поддержки создания изображений. Direct3D также является низкоуровневым API-интерфейсом, через который осуществляется визуализация всей графики в WPF. User32 используется для определения, какая программа получает тот или иной участок экрана.
3. Модель программирования и библиотека классов WPF
Чтобы подробнее разобраться в технологии Windows Presentation Foundation (WPF) необходимо ознакомиться с ее основными классами. Обобщенная иерархия классов приведена на рисунке 2.
Рисунок 2 – Иерархия основных классов WPF
Рассмотрим подробнее назначение данных классов.
DispatcherObject
Большинство объектов в WPF произошли от DispatcherObject, который предоставляет базовые конструкции для работы с параллелизмом и потоками. WPF базируется на системе обмена сообщениями, реализуемой диспетчером. Его работа очень похожа на обычную загрузку сообщений в Win32; в действительности диспетчер WPF использует сообщения User32 для выполнения перекрестных вызовов потоков.
DependencyObject
Одним из основных архитектурных принципов, используемых в построении WPF, является предпочтение свойств методам или событиям. Свойства являются декларативными и с их помощью проще указать цель, а не действие. Поддерживается также система для отображения содержимого пользовательского интерфейса на основе моделей (или данных). Такой подход оказал влияние на создание дополнительных свойств, к которым можно осуществить привязку в целях лучшего управления поведением приложения.
WPF предоставляет обширную систему свойств, описанную классом DependencyObject. Система свойств действительно является системой свойств «зависимостей» в том смысле, что она отслеживает зависимости между выражениями свойств и автоматически проверяет значения свойства при изменении зависимости. Например, если имеется наследующее свойство (например FontSize), то система автоматически обновляется при изменении свойства в родительском объекте элемента, наследующего значение.
Система свойств также предоставляет способ разреженного хранения значений свойств. Поскольку объекты могут иметь десятки (если не сотни) свойств, и большинство значений находятся в состоянии по умолчанию (унаследованы, задаются стилем и т. д.), не каждый экземпляр объекта должен иметь все определенные в нем свойства в полном объеме.
Наконец последняя новая особенность системы свойств – это понятие вложенных свойств. Элементы WPF базируются на принципе повторного использования композиции и компонентов. Часто бывает так, что некоторому содержащему элементу (например элементу макета Grid) требуются дополнительные данные о дочерних элементах для управления их поведением (например сведения о строках и столбцах). Вместо того чтобы сопоставлять все эти свойства с каждым элементом, любой объект может предоставить определения свойств для любого другого объекта.
Visual
Класс Visual предоставляет средства для построения дерева визуальных объектов, которые дополнительно включают инструкции по рисованию и метаданные о способе визуализации этих инструкций (обрезка, преобразование и другие).
Класс Visual является реальной точкой входа в систему композиции WPF. Класс Visual является точкой соединения между двумя подсистемами: управляемым API-интерфейсом и неуправляемым компонентом milcore.
WPF отображает данные, проходя по неуправляемым структурам данных под управлением milcore. Эти структуры, называемые узлами композиции, представляют собой иерархическое дерево отображения с инструкциями по визуализации в каждом узле.
При программировании WPF создаются элементы Visual и производные типы, которые осуществляют внутреннее взаимодействие с деревом композиции через этот протокол обмена сообщениями. Каждый элемент Visual в WPF может создать один, ни одного или несколько узлов композиции.
Здесь имеется один очень важный архитектурный момент – все дерево визуальных объектов и инструкций по рисованию кэшируется. С графической точки зрения WPF использует систему сохраненной визуализации. Это позволяет системе осуществлять перерисовку с высокой частотой без блокирования системы композиции при обратных вызовах, обращенных к коду пользователя.
Другим важным моментом, который не заметен на диаграмме, является то, как система в действительности выполняет композицию.
В User32 и GDI система работает в немедленном режиме системы обрезки. Когда требуется визуализация компонента, система устанавливает границы обрезки, вне которых компонент не может изменять точки, а затем компонент запрашивает рисование точек в этой области. Эта система работает очень хорошо в системах с ограниченной памятью, поскольку в случае каких-либо изменений приходится иметь дело только с измененным компонентом – два компонента никогда не воздействуют на цвет одной точки.
WPF использует «алгоритм художника» для модели рисования. Это означает, что вместо обрезки каждого компонента каждый компонент запрашивается для отрисовки, начиная с заднего плана и до переднего плана отображения. Это позволяет рисовать каждый компонент поверх отображения предыдущего компонента. Преимуществом этой модели является то, что можно создавать сложные, полупрозрачные фигуры. В сочетании с современным графическим оборудованием эта модель является относительно быстрой (чего нельзя было сказать о создании User32/ GDI).
Как упоминалось ранее, основным принципом WPF является переход к более декларативной, сфокусированной на свойствах модели программирования. В визуальной системе это проявляется в нескольких любопытных моментах.
Во-первых, если говорить о сохраненном режиме графической системы, он действительно отражает переход от обязательной модели DrawLine/DrawLine к модели, ориентированной на данные – new Line()/new Line(). Этот переход к управляемой данными визуализации позволяет выполнять сложные операции в инструкциях по рисованию, выражаемых с помощью свойств. Типы, получаемые из Drawing, являются эффективной объектной моделью для визуализации.
Во-вторых, оценивая систему анимации, можно увидеть, что она является практически полностью декларативной. Вместо обязательного вычисления разработчиком следующего положения или цвета можно выразить анимации как набор свойств для объекта анимации. Эти анимации могут выражать замыслы разработчика или проектировщика (переместить эту кнопку отсюда туда в течение 5 секунд), и система может определить наиболее эффективный способ для их выполнения.
UIElement
Класс UIElement определяет такие подсистемы, как «Макет», «Ввод данных» и «События».
Макет представляет собой основное понятие в WPF. Во многих системах либо присутствует фиксированный набор моделей для макетов (HTML поддерживает три модели для макетов: поток, абсолютное значение и таблицы), либо вообще нет модели для макета (User32 в действительности поддерживает только абсолютное размещение). Предпосылкой для создания WPF стало желание разработчиков и конструкторов иметь гибкую, расширяемую модель макета, которая управлялась бы значениями свойств, а не императивной логикой. На уровне UIElement вводится основное соглашение для макета – двухэтапная модель с передачей размера (Measure) и расположения (Arrange).
Measure позволяет компоненту определить требуемый размер. Этот этап является отдельным от Arrange, поскольку существует множество ситуаций, когда родительский элемент запрашивает несколько раз измерение дочернего элемента для определения его оптимального положения и размера. Тот факт, что родительские элементы запрашивают измерение дочерних, демонстрирует еще один ключевой принцип WPF – размер по содержимому. Все элементы управления в WPF поддерживает возможность изменения размера по размеру их содержимого. Это значительно упрощает локализацию и позволяет осуществлять динамическую разметку элементов в соответствии с изменением размеров. На этапе Arrange родительский элемент может расположить каждый дочерний элемент и определить его конечный размер.
В WPF существует также множество новшеств со стороны ввода данных. Вероятно, наиболее фундаментальным изменением в модели ввода для WPF является согласованная модель, согласно которой события ввода направляются через систему.
Каждое событие ввода преобразуется, по крайней мере, в два события – событие «предварительного просмотра» и фактическое событие. Все события в WPF имеют представление о маршрутизации через дерево элементов. События называются «всплывающими», если они перемещаются от конечной точки вверх по дереву к корню, и «нисходящими», если они начинаются от корня дерева и перемещаются вниз к конечной точке. События предварительного просмотра ввода перемещаются по нисходящей, позволяя любому элементу в дереве фильтровать или обрабатывать событие. Обычные события (не предварительного просмотра) перемещаются по восходящей от конечной точки вверх к корню.
Это разделение между нисходящим и всплывающим этапами делает возможным согласованную реализацию таких возможностей, как сочетания клавиш. В User32 сочетания клавиш реализуются посредством одной глобальной таблицы, содержащей все необходимые сочетания клавиш (Ctrl + N сопоставляется с командой «Создать»). В диспетчере приложения вызывается метод TranslateAccelerator, который будет анализировать входящие сообщения в User32 и определять их соответствие зарегистрированному сочетанию клавиш. В WPF это не работает, поскольку система полностью «компонуема» – любой элемент может обрабатывать и использовать любые сочетания клавиш. Наличие этой двухэтапной модели для ввода позволяет компонентам реализовать собственные методы TranslateAccelerator.
FrameworkElement
Элемент FrameworkElement можно рассматривать с двух разных сторон. Он представляет набор правил и настроек подсистем, введенных на нижнем уровне WPF. В нем также вводится набор новых подсистем.
Основная функциональность, представленная классом FrameworkElement, затрагивает макет приложения. Класс FrameworkElement строится на основе базового макета, представленного UIElement, и добавляет понятие «ячейки» макета, что упрощает для авторов макета создание согласованного набора управляемых свойствами семантик макета. Свойства, такие как HorizontalAlignment, VerticalAlignment, MinWidth и Margin (для нескольких именований), обеспечивают всем компонентам, производным от FrameworkElement, согласованное поведение внутри контейнеров макета.
FrameworkElement упрощает также доступ к API-интерфейсу для множества функций на уровне ядра WPF. Например, данный класс предоставляет прямой доступ к анимации с помощью метода BeginStoryboard. Объект Storyboard предоставляет способ создания скриптов нескольких анимаций вместо набора свойств.
Две наиболее важные вещи, представленные в FrameworkEle ment – это привязка данных и стили.
Подсистема привязки данных в WPF должна быть относительно знакома каждому, кто использовал Windows Forms или ASP.NET, чтобы создать пользовательский интерфейс приложения. В каждой из этих систем есть простой способ выразить, что для данного элемента необходимо привязать одно или более свойств к части данных. WPF обладает полным набором возможностей для привязки свойств, преобразования и привязки списка.
Одной из наиболее интересных возможностей привязки данных в WPF является введение шаблонов данных. Шаблоны данных позволяют декларативно указать способ визуализации фрагмента данных. Вместо создания настраиваемого пользовательского интерфейса, который может быть привязан к данным, можно обойти проблему и позволить данным определять отображение, которое будет создано.
Создание стилей – это облегченная форма привязки данных. Посредством создания стилей можно привязать набор свойств из общего определения к одному или нескольким экземплярам элемента. Стили применяются к элементу посредством либо явной ссылки (путем задания свойства Style), либо неявного связывания стиля с типом CLR элемента.
Control
Наиболее значимая возможность для элемента управления – это использование шаблонов. Если представлять себе систему композиции WPF как систему визуализации сохраненного режима, то шаблоны позволяют элементу управления описывать свою визуализацию в параметризированной, декларативной форме. ControlTemplate в действительности не более чем скрипт для создания набора дочерних элементов с привязками к свойствам, предлагаемым элементом управления.
Control предоставляет набор стандартных свойств Foreground, Background, Padding и т. д., которые авторы шаблона могут затем использовать для настройки отображения элемента управления. Реализация элемента управления обеспечивает модель данных и модель взаимодействия. Модель взаимодействия определяет набор команд (таких как «Закрыть» для окна) и привязки к действиям ввода (таким как нажатие «крестика» в верхнем углу окна). Модель данных предоставляет набор свойств либо для настройки модели взаимодействия, либо для настройки отображения (определяется шаблоном).
Это разделение между моделью данных (свойства), моделью взаимодействия (команды и события) и моделью отображения (шаблоны) позволяет полностью настроить внешний вид и поведение элемента управления.
Типичным аспектом модели данных элементов управления является модель содержимого. Если представить себе элемент управления, например Button, то можно увидеть, что он имеет свойство с именем Content типа Object. В Windows Forms и ASP.NET это свойство было бы строкой, но это ограничивает тип содержимого, который можно поместить на кнопку. Содержимое для кнопки может представлять собой простую строку, сложный объект данных или все дерево элементов. В случае объекта данных используется шаблон данных для создания отображения.
Класс Control также определяет элементы управления, которые могут взаимодействовать с пользователем (кнопки, списки, текстовые элементы и др.).
Прочие классы
Класс Shape является базовым для построения таких геометрических форм, как прямоугольник, эллипс, многоугольник, линия и путь.
Классы ContentControl и ItemsControl являются базовыми для элементов управления, которые могут иметь единственное содержимое или коллекцию соответственно.
Класс Panel является базовым для всех контейнеров компоновки элементов, которые могут содержать один или больше дочерних элементов.
4. Привязка данных WPF
Привязка данных является процессом, который устанавливает связь между пользовательским интерфейсом приложения и бизнеслогикой. Т.е., привязка данных – это механизм извлечения информации из объектов в интерфейсные элементы для отображения и, наоборот, заталкивания информации из элементов управления в объекты.
Привязка к данным обычно используется для того, чтобы поместить сервер или локальную конфигурацию данных в формы или другие элементы управления пользовательского интерфейса. В WPF эта концепция расширяется и уже включает привязку широкого диапазона свойств к различным источникам данных. В WPF свойства зависимостей элементов могут быть привязаны к объектам CLR (включая объекты ADO.NET или объекты, связанные с веб-службами и веб-свойствами) и к данным XML.
В привязке данных всегда участвуют две стороны: источник и приемник (целевой элемент) информации. Привязка данных может обеспечивать однонаправленный или двунаправленный обмен данными связанных свойств объектов. Чаще всего применяется однонаправленная привязка, целью которой является извлечение информации из источника и отображение ее на приемнике. Но в некоторых случаях различия между источником и приемником стираются, а иногда даже их роли меняются местами – приемник начинает поставлять данные источнику.
Вне зависимости от того, какие элементы привязываются и какой источник данных используется, каждая привязка всегда соответствует модели, показанной на рисунке 3:
Рисунок 3 – Модель привязки данных WPF
Обычно каждая привязка имеет четыре компонента: объект цели привязки, свойство цели, источник привязки и путь к значению используемого источника привязки. Например, если требуется связать содержимое TextBox со свойством Имя объекта Сотрудник, объектом цели является TextBox, свойством цели является свойство Text, используемым значением является Имя, а объектом источника является объект Сотрудник.
Синтаксис привязки данных имеет два варианта: расширения разметки и элементы свойств, но отличается деталями. Ключевым элементом привязки для любого варианта является определение объекта Binding из пространства имен System.Windows.Data. Этот элемент всегда устанавливается на стороне приемника привязки, кроме режима Mode=OneWayToSource. Приемник должен быть производным от класса DependencyObject и привязываемое свойство (целевое свойство) должно быть свойством зависимости. В свойства зависимостей встроена способность посылать или принимать уведомления об изменениях.
К источнику привязки предъявляется гораздо меньше требований. Связываемое свойство источника не обязано быть зависимым свойством. Главное, чтобы источник имел оповещающее событие, указывающее на изменение связываемого свойства. Источником привязки может быть любое открытое свойство, в том числе свойства других элементов управления, объекты среды CLR, элементы XAML, наборы данных ADO.NET, фрагменты XML и т. д. Для правильного применения привязки к сложным объектам данных технология WPF предоставляет два специализированных класса — XmlDataProvider и ObjectDataProvider.
Свойства зависимостей еще называют присоединенными. В версии XAML, предназначенной для WPF, присоединенные свойства работают только в том случае, если и тип, в котором свойство определено, и тип, к которому оно присоединяется, оба наследуют классу DependencyObject. С помощью свойств зависимостей язык XAML имеет возможность расширять типы за счет свойств, предоставляемых другими типами.
Направления привязки
Возможные типы и направления привязки данных показаны на рисунке 4.
Рисунок 4 – Типы привязки данных
Тип привязки элемента Binding определяется его свойством Mode, которое может принимать одно из значений перечисления BindingMode из пространства имен System.Windows.Data:
- Default установлен по умолчанию и зависит от типа привязываемого свойства на стороне приемника (целевого свойства). Действует как режим двухсторонней привязки TwoWay для свойств, доступных для редактирования в пользовательском интерфейсе, таких как TextBox.Text или CheckBox.Checked, либо как односторонняя привязка OneWay для иных свойств. Чтобы не полагаться на настройки по умолчанию, следует взять себе за правило всегда явно устанавливать параметр направления привязки;
- OneTime – односторонняя начальная привязка, когда значение целевого свойства устанавливается по значению источника только один раз: при инициализации, программной замены привязанного объекта-источника на новый, при изменении свойства DataContext или в результате программного вызова метода BindingExpression.UpdateTarget(). Иные поступающие уведомления об изменениях на стороне источника приемником учитываться не будут;
- OneWay – односторонняя привязка, когда целевое свойство обновляется при изменении свойства источника. Каждый раз при изменении на стороне источника поток данных направлен от источника к целевому объекту;
- OneWayToSource – организует однонаправленную привязку, как и OneWay, только выражение привязки помещается в источник. Этот трюк может понадобиться в том случае, когда привязываемое свойство-приемник не является свойством зависимости, а свойство источника все-таки наследует классу DependencyObject;
- TwoWay – двухсторонняя привязка, когда целевое свойство обновляется при изменении свойства источника и свойствоисточник обновляется при изменении целевого свойства. Иными словами, режим привязки TwoWay отправляет данные от источника к целевому объекту, а в случае изменения значения свойства целевого объекта данные отправляются обратно от целевого объекта к источнику.
Создание привязки данных
Есть несколько способов для указания объекта источника привязки. С помощью свойства DataContext родительского элемента удобно привязывать несколько свойств к одному источнику. Однако иногда удобнее указывать источник привязки в отдельных объявлениях привязки. Например, можно указать источник привязки, установив свойство Source непосредственно в объявлении привязки кнопки, как показано в следующем примере:
<Button Width="150" Height="30"
Background="{Binding Source={StaticResource myDataSource},
Path=ColorName}">I am bound to be RED!</Button>
Кроме установки свойства DataContext непосредственно в элементе, наследования значения DataContext от предка и явного указания источника привязки установкой свойства Source на Binding (например, кнопки в предыдущем примере), можно также использовать свойство ElementName или свойство RelativeSource для указания источника привязки. Свойство ElementName полезно при связывании с другими элементами приложения, например при использовании ползунка для настройки ширины кнопки. Свойство RelativeSource используется при установке связывания в ControlTemplate или Style.
Рассмотрим также возможности указания пути к значению для привязки. Если источник привязки является объектом, для указания значения, используемого для привязки используется свойство Path. При связывании данных XML используется свойство XPath. В некоторых случаях удобно применить свойство Path, даже если это данные XML. Например, если требуется получить доступ к свойству Имя возвращаемого XmlNode (в результате выполнения запроса XPath), следует использовать свойство Path в дополнении к свойству XPath.
Обратите внимание, что хотя и было подчеркнуто, что путь Path к используемому значению является одним из четырех необходимых компонентов связывания, в скриптах, которые требуется привязать ко всему объекту, используемое значение должно быть таким же, как и у объекта источника привязки. В этих случаях оно применяется без указания Path. Рассмотрим следующий пример:
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="true"/>
В приведенном выше примере используется синтаксис пустой привязки. В этом случае ListBox наследует DataContext от родительского элемента DockPanel (не показан в этом примере). Если путь не указан, по умолчанию производится привязка к всему объекту. Другими словами, в этом примере путь не был указан, так как мы выполнили привязку свойства ItemsSource ко всему объекту.
Кроме привязки к коллекции, этот скрипт полезен также для возможности привязки ко всему объекту, а не только к одному свойству объекта. Например, если объект источника является объектом строкового типа, и всего лишь нужна привязка к самой строке. Другим распространенным скриптом является необходимость привязки элемента к объекту с несколькими свойствами.
Обратите внимание, что, возможно, потребуется применить пользовательскую логику, чтобы данные были применимы к привязанному свойству цели. Пользовательская логика может быть представлена в виде пользовательского преобразователя (если не существует преобразования типа по умолчанию).
5. Графические возможности WPF: двухмерная и трехмерная геометрия
WPF представляет обширный, масштабируемый и гибкий набор графических возможностей, которые имеют следующие преимущества:
- графика, не зависящая от разрешения и устройства. Основной единицей измерения в графической системе WPF является аппаратно-независимая точка, которая составляет 1/96 часть дюйма независимо от фактического разрешения экрана и предоставляет основу для создания изображения, независимого от разрешения и устройства. Каждый аппаратно-независимый пиксель автоматически масштабируется в соответствии с числом точек на дюйм в системе, в которой он отображается;
- повышенная точность. В системе координат WPF используются числа с плавающей запятой двойной точности, вместо одиночной точности. Значения преобразований и прозрачности также выражаются с помощью чисел двойной точности. Кроме того, WPF поддерживает широкую цветовую палитру (scRGB) и предоставляет встроенную поддержку для управления входными данными из различных цветовых пространств;
- дополнительная поддержка графики и анимации. WPF упрощает программирование графики за счет автоматического управления анимацией. Разработчик не должен заниматься обработкой сцен анимации, циклами визуализации и билинейной интерполяцией. Кроме того, WPF предоставляет поддержку проверки нажатия и полную поддержку альфа-компоновки;
- аппаратное ускорение. Графическая система WPF использует преимущества графического оборудования, чтобы уменьшить использование ЦП.
Двухмерная геометрия
WPF предоставляет стандартный набор двухмерных фигур. Однако, возможно, потребуется создать пользовательские фигуры для облегчения разработки настраиваемого пользовательского интерфейса. В этих целях WPF предоставляет геометрические объекты. Основные классы двухмерной геометрии представлены на рисунке 5.
Рисунок 5 – Основные классы двухмерной геометрии
Объекты Path могут быть использованы для рисования замкнутых, открытых, составных фигур и даже кривых поверхностей.
Объекты Geometry могут использоваться для отсечения, проверки нажатия и отрисовки данных двухмерной графики.
PathGeometry и CombinedGeometry позволяют описывать геометрию двумерной фигуры. Данные геометрические описания имеют множество применений. Например, их можно использовать при определении фигуры для вывода на экран или при проверке нажатия и задании областей отсечения. Помимо этого геометрические описания можно использовать для определения пути анимации.
Объекты класса Geometry могут быть простыми, такими как прямоугольники и круги, или сложными, созданными из двух или более геометрических объектов. Более сложные геометрические объекты могут быть созданы с помощью классов PathGeometry и StreamGeometry, позволяющих описывать дуги и кривые.
Поскольку класс Geometry является наследником класса Freezable, то объекты класса Geometry обладают рядом специальных возможностей: их можно объявлять как ресурсы, которые могут совместно использоваться несколькими объектами, делать доступными только для чтения с целью повышения производительности, клонировать и делать потокобезопасными.
Сходство классов Geometry и Shape заключается в том, что они описывают двумерные фигуры, но между ними существуют важные отличия.
Базовым классом для всех геометрических объектов является абстрактный класс Geometry. Классы, являющиеся производными от класса Geometry, можно сгруппировать в три категории: простые объекты Geometry, объекты PathGeometry и составные объекты Geometry.
Простые геометрические классы включают классы LineGeometry, RectangleGeometry и EllipseGeometry и используются для создания основных геометрических фигур, таких как линии, прямоугольники и окружности:
- объект класса LineGeometry определяется путем задания для линии начальной и конечной точек;
- объект класса RectangleGeometry определяется структурой Rect, которая задает его относительное положение, высоту и ширину. Можно создать скругленный прямоугольник, задав свойства RadiusX и RadiusY;
- объект класса EllipseGeometry определяется центральной точкой, x-радиусом и y-радиусом.
Эти же фигуры, а также более сложные фигуры могут быть созданы с помощью класса PathGeometry или путем объединения геометрических объектов, но данные классы предоставляют более простые средства для создания основных геометрических фигур.
Класс PathGeometry и его облегченный эквивалент класс StreamGeometry, предоставляют средства для описания нескольких сложных фигур, состоящих из дуг, кривых и линий.
Как и класс PathGeometry, класс StreamGeometry определяет сложную геометрическую форму, которая может содержать кривые, дуги и линии. В отличие от объектов PathGeometry, содержимое StreamGeometry не поддерживает привязку данных, анимацию или изменения. Объект используется StreamGeometry для описания сложной геометрии, но без служебных данных поддержки привязки данных, анимации или изменения. Ввиду своей эффективности, класс StreamGeometry лучше всего подходит для описания декоративных элементов.
Объект CombinedGeometry и метод Combine объединяют области, занимаемые содержащимися в них объектами Geometry. Перечисление GeometryCombineMode задает способ комбинирования объектов Geometry. Возможны следующие значения свойства GeometryCombineMode: Union, IntersectExclude и Xor.
В следующем примере объект CombinedGeometry определен режимом объединения Union. Geometry1 и Geometry2 задаются как круги с одинаковыми радиусами, но с центрами, смещенными на 50.
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<!-- Combines two geometries using the union combine mode. -->
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="75,75" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="125,75" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
Трехмерная геометрия
Начало системы координат WPF для двумерной графики отсчитывается от левого верхнего угла области рисования (обычно экрана). Положительные значения оси x откладываются вправо, а положительные значения оси y – сверху вниз. Однако в трехмерной системе координат начало располагается в центре отрисовываемой области, положительные значения оси x откладываются вправо, оси y – снизу вверх, а оси z – из центра к наблюдателю. Классы трехмерной геометрии представлены на рисунке 6.
Рисунок 6 – Основные классы трехмерной геометрии
Разработчики, работающие в двухмерных координатах, привыкли к размещению графических примитивов на двухмерном экране. При создании трехмерной сцены важно помнить, что фактически создается трехмерное представление двухмерных объектов. Поскольку трехмерная сцена выглядит по-разному в зависимости от точки наблюдения, необходимо указать эту точку наблюдения. Указать эту точку наблюдения для сцены трехмерный позволяет класс Camera.
Другой способ понимания того, как представляется трехмерная сцена на двухмерной поверхности, – это описание сцены как проекции на поверхность просмотра. Класс ProjectionCamera позволяет указать различные проекции и их свойства для изменения т ого, как наблюдатель видит трехмерные модели. Класс PerspectiveCamera указывает проекцию сцены в перспективе. Другими словами, PerspectiveCamera предоставляет точку схода перспективы. Можно указать положение камеры в пространстве координат сцены, направление и поле зрения камеры и вектор, определяющий направление «вверх» в сцене.
Класс OrthographicCamera указывает ортогональную проекцию трехмерной модели на визуальную двухмерную поверхность. Подобно другим камерам, она указывает позицию, направление просмотра и направление «вверх». Однако в отличие от PerspectiveCamera, класс OrthographicCamera описывает проекцию, которая не включает ракурс перспективы.
Model3D – это абстрактный базовый класс, представляющий универсальный трехмерный объект. Чтобы построить трехмерную сцену, необходимо, чтобы некоторые объекты для отображения и объекты, составляющие граф сцены, наследовались от класса Model3D. В настоящее время приложение WPF поддерживает моделирование геометрических объектов с помощью класса GeometryModel3D. Свойство Geometry данной модели принимает примитив сетки.
Для определения характеристик поверхности модели приложение WPF использует абстрактный класс Material. Его конкретные подклассы определяют некоторые характеристики внешнего вида поверхности модели, и каждый из них предоставляет свойство Brush, которому можно передать значение SolidColorBrush, TileBrush или VisualBrush:
- класс DiffuseMaterial определяет, что кисть будет применена к модели, как если бы она была освещена рассеянным светом. Использование класса DiffuseMaterial больше всего напоминает применение кистей непосредственно в двухмерных моделях; поверхности модели не отражают свет, как блестящие поверхности;
- класс SpecularMaterial определяет, что кисть будет применена к модели, как если бы поверхность модели была твердой или блестящей, способной отражать блики. Можно установить степень гладкости или «глянца» текстуры, задав значение свойства SpecularPower;
- класс EmissiveMaterial позволяет указать, что текстура будет применена, как если бы модель излучала свет, эквивалентный цвету кисти. Это не делает модель светящейся; однако это иначе влияет на затенение, чем если бы текстура была создана с пом ощью класса DiffuseMaterial или SpecularMaterial.
6. Дополнительные возможности WPF
Форматы документов
Для облегчения отрисовки текста высокого качества WPF предоставляет следующие возможности:
- поддержка шрифта OpenType;
- улучшения ClearType;
- высокая производительность, которая использует преимущества аппаратного ускорения;
- интеграция текста с мультимедиа, графикой и анимацией;
- механизмы резервирования и поддержки международного шрифта.
WPF предоставляет встроенную поддержку работы с тремя типами документов: документами нефиксированного формата, документами фиксированного формата и документами формата XPS (XML Paper Specification).WPF также предоставляет службы для создания и просмотра документов, управления документами, добавления заметок, упаковки и печати документов.
Документы нефиксированного формата разработаны для оптимизации просмотра и читаемости посредством динамической настройки и обновления содержимого при изменении размера окна и параметров дисплея.
Документы фиксированного формата предназначены для приложений, в которых требуется точное представление вида «что видишь, то и получишь» (режим полного соответствия изображения на экране и распечатки WYSIWYG), особенно по отношению к печати. Документы фиксированного формата обычно используются при подготовке публикаций с помощью настольных издательских средств, обработке текста и разметке формы, где строгое соблюдение исходного дизайна страницы является обязательным.
В документах фиксированного формата поддерживается точное размещение содержимого независимо от устройства. Например, документ фиксированного формата отображается на мониторе с разрешением 96 точек на дюйм точно так же, как при печати на лазерном принтере с разрешением 600 точек на дюйм или на фотонаборной машине с разрешением 4800 точек на дюйм. Макет документа остается одинаковым во всех случаях, хотя качество документа варьируется в зависимости от возможностей каждого устройства.
Документы Формат XPS (XML Paper Specification) построены на основе документов фиксированного формата WPF. Документы XPS описываются схемой на основе XML, которая фактически представляет разбитый на страницы электронный документ. Открытый кроссплатформенный формат документов XPS предназначен для упрощения создания, печати и архивирования разбитых на страницы документов, а также организации совместного доступа.
Настройка приложений WPF
Зачастую основных возможностей недостаточно для создания особых и визуально впечатляющих возможностей для пользователя и для управления ими. Стандартные элементы управления WPF могут не подходить для создания требуемого внешнего вида приложения. Данные могут отображаться не самым лучшим образом. Общее взаимодействие с пользователем приложения может не подходить к используемому по умолчанию внешнему виду и темам Windows. Во многих случаях технологии презентации требуется визуальное расширение, как и любой другой тип расширения.
По этой причине WPF предоставляет разнообразные механизмы для создания уникальных взаимодействий с пользователем, включая расширенную модель содержимого для элементов управления, триггеров, а также шаблонов, стилей, ресурсов Пользовательский интерфейс, тем и обложек элементов управления и данных.
Главной задачей большей части элементов управления WPF является отображение содержимого. В WPF тип и количество элементов, которые могут составлять содержимое элемента управления, называется моделью содержимого элемента управления. Некоторые элементы управления могут содержать один элемент и один тип содержимого. Например, содержимое TextBox – строковое значение, которое присваивается свойству Text.
Однако другие элементы управления, могут содержать несколько элементов с разными типами содержимого; например содержимое элемента управления Button, заданное в свойстве Content, может содержать разнообразные элементы, включая элементы управления макета, текст, изображения и фигуры.
Хотя главной задачей разметки на XAML и является реализация внешнего вида приложения, этот язык также можно использовать для реализации некоторых действий, выполняемых приложением. Например, с помощью триггеров изменять внешний вид приложения в зависимости от действий пользователя.
Работа с шаблонами
Пользовательские интерфейсы по умолчанию для элементов управления WPF обычно создаются из других элементов управления и фигур. Например, Button состоит из элементов управления ButtonChrome и ContentPresenter. ButtonChrome обеспечивает стандартный внешний вид кнопки, в то время как ContentPresenter отображает содержимое кнопки, заданное свойством Content.
Иногда внешний вид элемента управления по умолчанию может не сочетаться с общим внешним видом приложения. В этом случае можно с помощью ControlTemplate изменить внешний вид элемента управления без изменения его содержимого и поведения. В то время как шаблон элемента управления позволяет задавать внешний вид элемента управления, шаблон данных позволяет задавать внешний вид содержимого элемента управления. Шаблоны данных часто используются для улучшения способа отображения данных, связанных с элементом управления.
Стили позволяют разработчикам и конструкторам выработать стандарт внешнего вида для продукта. WPF предоставляет строгую модель стилей, основой которой служит элемент Style.
Элементы управления в приложении должны использовать один и тот же внешний вид, который может включать любые шрифты и цвета фона для шаблонов элементов управления, шаблонов данных и стилей. С помощью поддержки WPF можно инкапсулировать эти ресурсы в одном расположении для повторного использования.
Существует множество областей действия ресурсов, включая следующие, перечисленные в порядке, в котором они применяются:
- отдельные элементы управления (использующие наследуемое свойство FrameworkElement.Resources);
- Window или Page (также использующие наследуемое свойство FrameworkEle ment.Resources);
- Application (использующее свойство Application.Resources). Разнообразие областей действия обеспечивает гибкость по отношению к способу определения и совместного использования ресурсов.
Пользовательские элементы управления
В WPF существует три модели для создания нового элемента управления. Каждая модель предназначена для определенного скрипта и требует, чтобы пользовательский элемент управления был производным от конкретного базового класса WPF:
- модель пользовательского элемента управления. Пользовательский элемент управления производится из UserControl и состоит из одного или нескольких других элементов управления;
- модель элемента управления. Пользовательский элемент управления производится из класса Control и используется для построения реализаций, в которых внешний вид и поведение разделены с помощью шаблонов, подобно большей части элементов управления WPF. Создание элемента управления, производного от Control, предоставляет по сравнению с пользовательскими элементами управления большую свободу для создания нестандартного пользовательского интерфейса, но может потребовать дополнительных усилий;
модель элемента .NET Framework. Пользовательский элемент управления производится от FrameworkElement, когда его внешний вид определяется пользовательской логикой визуализации (не шаблонами).
7. Практическое задание. Введение в WPF. Хостинг WPFэлемента управления в приложении WinForms
7.1. Создание пользовательского элемента управления (WPF User Control)
1. Создайте проект WPF User Control Library с именем WPFControlLibrary1 (рисунок 1):
Рисунок 1 – Создание проекта
2. Откройте файл UserControl1.xaml в конструкторе WPF
(рисунок 2):
3. Замените в тексте XAML облаcть <Grid> </Grid> следующим кодом:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="402*" />
<ColumnDefinition Width="160*" />
</Grid.ColumnDefinitions>
</Grid>
Рисунок 2 – Открытие файла в конструкторе WPF
При этом рабочая область создаваемого элемента управления будет иметь вид, показанный на рисунке 3.
Рисунок 3 – Рабочая область пользовательского элемента управления
4. Добавьте в рабочую область создаваемого элемента управления Кнопку (Button) и Поле ввода (TextBox) так, как показано на рисунке 4.
Рисунок 4 – Добавление компонентов
Выполните настройку свойств согласно рисунку 5 и таблице 1. Свойства отображаются в окне «Properties».
Рисунок 5 – Настройка свойств элементов управления
Таблица 1 – Настройка свойств
Свойство | Значение |
Сетка (Crid) | |
1й столбец — ColumnDefinition — Width | 189* |
2й столбец — ColumnDefinition — Width | 85* |
RowDefinition — Height | 40* |
Кнопка (Button) | |
Name | button1 |
Margin | 0,0,11,0 |
Grid.Column | 1 |
FontSize | 14 |
BitmapEffect | DropShadowBitmapEffect |
Content | Найти |
Поле ввода (TextBox) | |
BorderThickness | 3 |
Margin | 0,0,11,0 |
Name | textBox1 |
FontSize | 14 |
BitmapEffect | DropShadowBitmapEffect |
Text | Строка поиска |
Элемент управления (UserControl) | |
Height | 39 |
Width | 283 |
5. Текст файла UserControl1.xaml после всех настроек таблицы 1 имеет вид:
UserControl x:Class="WpfControlLibrary1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation "
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="39" Width="283">
<Grid Height="27" Width="274">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189*" />
<ColumnDefinition Width="85*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40*" />
</Grid.RowDefinitions>
<Button Name="button1" Margin="0,0,11,0" Grid.Column="1" FontSize="14">
<Button.BitmapEffect>
<DropShadowBitmapEffect />
</Button.BitmapEffect> Найти</Button>
<TextBox Margin="0,0,11,0" Name="textBox1" FontSize="14" BorderThickness="3">Строка поиска
<TextBox.BitmapEffect>
<DropShadowBitmapEffect />
</TextBox.BitmapEffect>
</TextBox>
</Grid>
</UserControl>
6. Добавим в проектируемый составной элемент управления возможность обработки событий изменения текста в поле ввода и нажатия на кнопку. Для этого необходимо перейти в редактор файла UserControl1.xaml.cs и перед строчкой
public partial class UserControl1 : UserControl
добавить две строки:
public delegate void TextChangedEventHandler(object sender, string newValue);
public delegate void ClickEventHandler();
7. Далее модифицировать код класса UserControl1 по следующему образцу:
public partial class UserControl1: UserControl
{
public UserControl1()
{
InitializeComponent();
}
public event TextChangedEventHandler TextChanged; public event ClickEventHandler Click;
public string Text
{
get { return (string)GetValue(TextProperty); } set
{
SetValue(TextProperty, value);
}
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new
FrameworkPropertyMetadata("Строка поиска", new PropertyChangedCallback(OnTextChanged)));
private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
(obj as UserControl1).UpdateText (args.NewValue.ToString());
}
private void UpdateText(string NewText)
{
textBox1.Text = NewText; TextChanged(this, NewText);
}
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
SetValue(TextProperty, textBox1.Text);
}
private void button1_Click(object sender, RoutedEventArgs
e)
{
Click();
}
}
8. Перейти в редактор XAML-кода и в строке, которая начинается с <Button добавить ссылку на обработчик, написав следующее
Click="button1_Click"
Т.е. видоизмененная строка будет иметь вид:
<Button Click="button1_Click" Name="button1" Margin="0,0,11,0" Grid.Column="1" FontSize="14">
9. В редакторе XAML-кода и в строке, которая начинается с
<TextBox добавить ссылку на обработчик, написав следующее:
TextChanged="textBox1_TextChanged"
Т.е. видоизмененная строка будет иметь вид:
<TextBox TextChanged="textBox1_TextChanged" Margin="0,0,11,0" Name="textBox1" FontSize="14" BorderThickness="3">
10. Построение пользовательского составного элемента управления завершено. Для его компиляции и сборки необходимо выполнить команду из меню Build->Build WpfControlLibrary1.
7.2. Построение приложения WinForms для демонстрации хостинга WPF-элемента управления
1. Создайте проект Windows Forms Application с именем WinFormsApp1.
2. Добавить к текущему solution проект с разработанным составным элементом управления.
Для этого перейти в Solution Explorer, щелкнуть правой кнопкой мыши по строке «Solution WinFormsApp1 (1 project)», в появившемся контекстном меню выбрать пункт Add-> Existing Project… Далее выбрать файл проекта созданного элемента управления (WpfControlLibrary1.csproj).
3. После этого в Toolbox появится ссылка на добавленный элемент управления (рисунок 6).
4. Добавьте элемент UserControl1 на форму и растяните его, как показано на рисунке 7.
Рисунок 6 – Пользовательский элемент управления в Toolbox
Рисунок 7 – Работа с пользовательским элементом управления
Фактически, на форму был добавлен экземпляр класса ElementHost, который отвечает за интеграцию WPF-элементов в приложение WinForms. Добавление созданного элемента управления можно было выполнить и другим способом: добавить на форму экземпляр класса ElementHost и затем в нем подключить UserControl1 (рисунок 8).
5. Перейдите к редактированию кода и подключите UserControl1 в инструкции using:
using WpfControlLibrary1;
Рисунок 8 – Второй способ подключения пользовательского элемента управления
6. Задайте указатель на созданный элемент управления в классе своего приложения, т. е. добавьте следующую строку перед конструктором:
UserControl1 wpf_control;
7. В конструктор внести следующие изменения:
public Form1()
{
InitializeComponent();
wpf_control = (UserControl1)elementHost1.Child;
}
Таким образом, явно указано, что wpf_control необходимо ассоциировать с elementHost1.Child.
8. Далее необходимо добавить возможность работать с полем ввода и кнопкой составного элемента управления. Поэтому необходимо подключить соответствующие обработчики событий, которые были созданы при проектировании UserControl1. Модифицированный конструктор:
public Form1()
{
InitializeComponent();
wpf_control = (UserControl1)elementHost1.Child; wpf_control.TextChanged += new TextChangedEventHandler
(wpf_control_TextChanged);
wpf_control.Click += new ClickEventHandler
(wpf_control_Click);
}
При подключении события на помощь приходит IntelliSense (рисунок 9):
Рисунок 9 – Подключение события
Двойное нажатие на клавишу TAB обеспечивает и создание заготовки под функцию, где можно обрабатывать реакцию программы на нажатие кнопки «Найти»:
void wpf_control_Click()
{
}
9. После подключения обработчиков и создания заготовок, опишите функции нажатия на кнопку «Найти» и изменения строки ввода так, как показано ниже:
void wpf_control_TextChanged(object sender, string newValue)
{
wpf_control.Text = newValue;
}
void wpf_control_Click()
{
MessageBox.Show(wpf_control.Text);
}
10. Откомпилируйте и запустите приложение, проверьте его работоспособность.
7.3. Добавление в проект готового элемента управления
1. Внесите изменения в XAML-код созданного элемента управления согласно листингу:
<UserControl x:Class="WpfControlLibrary5.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="39" Width="283">
<Grid Height="27" Width="274">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="189*" />
<ColumnDefinition Width="85*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40*" />
</Grid.RowDefinitions>
<Button Click="button1_Click" Name="button1" Margin="0,0,11,0" Grid.Column="1" FontSize="14" >
<Button.BitmapEffect>
<DropShadowBitmapEffect />
</Button.BitmapEffect> Найти
<Button.Background>
<LinearGradientBrush EndPoint="1,1"
StartPoint="0,0">
/>
/>
<GradientStop Color="LightBlue" Offset="0.1" />
<GradientStop Color="LightCoral" Offset="0.25"
<GradientStop Color="LightCyan" Offset="0.75"
<GradientStop Color="LightGreen" Offset="1" />
</LinearGradientBrush>
</Button.Background>
<Button.BorderBrush>
<LinearGradientBrush EndPoint="1,1"
StartPoint="0,0">
/>
/>
<GradientStop Color="LightBlue" Offset="0.1" />
<GradientStop Color="LightCoral" Offset="0.25"
<GradientStop Color="LightCyan" Offset="0.75"
<GradientStop Color="LightGreen" Offset="1" />
</LinearGradientBrush>
</Button.BorderBrush>
</Button>
<TextBox TextChanged="textBox1_TextChanged" Margin="0,0,11,0" Name="textBox1" FontSize="14" BorderThickness=" 3"
>Строка поиска
<TextBox.BitmapEffect>
<DropShadowBitmapEffect />
</TextBox.BitmapEffect>
<TextBox.Foreground>
<LinearGradientBrush EndPoint="1,1"
StartPoint="0,0">
<GradientStop Color="Orange" Offset="0.25" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush>
</TextBox.Foreground>
<TextBox.BorderBrush>
<LinearGradientBrush EndPoint="1,1"
StartPoint="0,0">
<GradientStop Color="Orange" Offset="0.25" />
<GradientStop Color="Green" Offset="1" />
</LinearGradientBrush>
</TextBox.BorderBrush>
</TextBox>
</Grid>
</UserControl>
2. Скомпилируйте новую версию элемента управления. Запустите приложение WinForms, чтобы оценить работу обновленного элемента управления.
3. Попробуйте самостоятельно настроить какие-либо свойства.
4. Создайте и добавьте в свой рабочий проект с ранее выполненными практическими заданиями какой-либо WPF-элемент управления и запрограммируйте его на выполнение каких-либо действий.