Рисованные элементы
63WPF --- Шаблоны и пользовательские элементы управления WPF --- Рисованные элементы
Большинство элементов WPF используют композицию для создания своего внешнего представления. Другими словами, типичный элемент строит себя из других, более фундаментальных элементов. Вы уже видели, как работает эта модель. Например, составные элементы пользовательского элемента управления определяются с помощью кода разметки, который обрабатывается таким же образом, как XAML-разметка пользовательского окна. Вы определяете визуальное дерево пользовательского элемента управления с применением управляющего шаблона. А при создании пользовательской панели какие-либо визуальные детали вообще не нужны. Составные элементы предоставляются потребителем и добавляются в коллекцию Children.
Такой акцент отличается от того, что имело место в предшествующих технологиях построения пользовательских интерфейсов, таких как Windows Forms. В Windows Forms некоторые элементы управления рисуют себя, используя библиотеку User32, которая является частью Windows API, но наиболее специализированные элементы полагаются на классы рисования GDI+ для отображения себя "с нуля".
Поскольку Windows Forms не предоставляет высокоуровневых графических примитивов, которые можно было бы добавить непосредственно к пользовательскому интерфейсу (вроде прямоугольников, эллипсов и путей WPF), всякий элемент управления, который нуждается в нестандартном визуальном представлении, требует специального кода отображения.
Конечно, только композиция может завести вас так далеко. В конечном итоге, некоторые классы должны взять на себя ответственность за рисование содержимого. В WPF этот момент находится глубоко в дереве элементов. В типичном окне отображение состоит из индивидуальных фрагментов текста, фигур и растровых изображений, а не высокоуровневых элементов.
Метод OnRender()
Чтобы выполнить специальное отображение, элемент должен переопределить метод OnRender(), унаследованный от базового класса UIElement. Метод OnRender() не обязательно заменяет композицию. Некоторые элементы управления применяют OnRender() для рисования визуальных деталей и используют композицию для компоновки прочих элементов на своей поверхности. Примером может служить класс Border, который рисует свою рамку в методе OnRender(), и класс Panel, рисующий свой фон в методе OnRender(). Как Border, так и Panel поддерживают дочернее содержимое, и это содержимое отображается поверх специально отображаемых деталей.
Метод OnRender() принимает объект DrawingContext, который предлагает набор полезных методов рисования содержимого. Он используется для рисования содержимого объекта Visual. Ключевое отличие рисования в методе OnRender() состоит в том, что вы не создаете явно и не закрываете DrawingContext. Это связано с тем, что несколько разных методов OnRender() могут совместно использовать один и тот же DrawingContext. Например, элемент-наследник может выполнять некоторое специальное отображение и вызывать реализацию OnRender() в базовом классе для рисования дополнительного содержимого. Это работает, потому что WPF автоматически создает объект DrawingContext в начале этого процесса и закрывает его, когда он больше не нужен.
Формально метод OnRender() в действительности не рисует содержимое на экране. Вместо этого он рисует его в объекте DrawingContext, a WPF затем кэширует эту информацию. WPF определяет, когда элемент нуждается в перерисовке, и тогда выводит то, что было создано в DrawingContext. В этом и состоит сущность графической системы WPF вы определяете содержимое, a WPF незаметно управляет его рисованием и обновлением.
Наиболее удивительная деталь механизма отображения WPF заключается в том, что в действительности выполняют его совсем немного классов. Большинство классов построено на основе других, более простых классов, и нужно "нырнуть" достаточно глубоко в дерево элементов, чтобы найти класс, который действительно переопределяет метод OnRender(). Ниже описаны некоторые из таких классов:
- Класс TextBlock
Всякий раз, когда рисуется текст, применяется объект TextBlock, использующий свой метод OnRender() для его отображения.
- Класс Image
Класс Image переопределяет метод OnRender() для рисования содержимого изображения, используя метод DrawingContext.DrawImage().
- Класс MediaElement
Класс MediaEiement переопределяет метод OnRender() для рисования видеокадра, если он используется для воспроизведения видеофайла.
- Классы фигур
Базовый класс Shape переопределяет метод OnRender() для рисования своего внутреннего объекта Geometry с помощью метода DrawingContext.DrawGeometry(). Этот объект Geometry может представлять эллипс, прямоугольник или более сложные пути, состоящие из отрезков прямых и кривых линий, в зависимости от специфичного класса-наследника Shape. Многие элементы используют фигуры для рисования небольших визуальных деталей.
- Классы Chrome
Классы, подобные ButtonChrome и ListBoxChrome, рисуют внешнее представление общего элемента управления и помещают указанное содержимое внутрь. Многие другие классы-наследники Decorator, такие как Border, также переопределяют метод OnRender().
- Классы панелей
Хотя содержимое панелей представлено ее дочерними элементами, метод OnRender() заботится о рисовании прямоугольника с цветом фона, если установлено свойство Background.
Часто реализация OnRender() бывает обманчиво простой. Например, вот как выглядит код отображения любого класса-наследника Shape:
protected override void OnRender(DrawingContext drawingContext)
{
this.EnsureRenderedGeometry();
if (this._renderedGeometry != Geometry.Empty)
{
drawingContext.DrawGeometry(this.Fill, this.GetPen(),
this._renderedGeometry);
}
Вспомните, что переопределение OnRender() — не единственный способ отобразить содержимое и добавить его в пользовательский интерфейс. Можно также создать объект DrawingVisual и добавить его к UIElement с помощью метода AddVisualChild() (а также реализовать несколько других деталей). Затем можно вызвать DrawingVisual.RenderOpen(), чтобы извлечь DrawingContext и использовать его для рисования содержимого.
Некоторые элементы применяют эту стратегию в WPF для отображения ряда графических деталей поверх остального содержимого элемента. Это можно видеть на примере указателей перетаскивания, индикаторов ошибок и рамок фокуса. Во всех этих случаях подход на основе DrawingVisual позволяет элементу рисовать поверх другого содержимого, а не под ним. Но все-таки в основном все отображение происходит в выделенном методе OnRender().