Рисунки

26

Как уже известно, абстрактный класс Geometry представляет фигуру или путь. Абстрактный класс Drawing играет дополняющую роль. Он представляет двухмерные рисунки — другими словами, содержит всю информацию, которая нужна для вывода части векторного или растрового изображения.

Хотя существует несколько типов классов рисунков, среди них GeometryDrawing — один из тех, которые работают с рассмотренными ранее геометриями. Он добавляет детали очерчивания границ и заполнения областей — т.е. описывает, как геометрия должна быть нарисована. Объект GeometryDrawing можно воспринимать как единственную фигуру в части векторного рисунка. Например, можно преобразовать стандартный файл .wmf в коллекцию объектов GeometryDrawing, готовых к вставке в пользовательский интерфейс.

Давайте рассмотрим простой пример. Ранее было показано, как определяется простой объект PathGeometry, представляющий треугольник:

<PathGeometry>
   <PathFigure IsClosed="True" StartPoint="10,100">
      <LineSegment Point="100,100" />
      <LineSegment Point="100,50" />
   </PathFigure>
</PathGeometry>

Этот объект PathGeometry можно применить для построения GeometryDrawing следующим образом:

<GeometryDrawing Вrush="Yellow">
   <GeometryDrawing.Pen>
     <Pen Brush="Blue" Thickness = "3"></Pen>
   </GeometryDrawing.Pen>
   <GeometryDrawing.Geometry>
      <PathGeometry>
         <PathFigure IsClosed="True" StartPoint="10,100">
            <LineSegment Point="100,100" />
            <LineSegment Point="100,50" />
         </PathFigure>
      </PathGeometry>
   </GeometryDrawing.Geometry>
</GeometryDrawing>

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

Класс GeometryDrawing вводит новую деталь: класс System.Windows.Media.Pen. Класс Pen (перо) предоставляет свойства Brush и Thickness, использованные в предыдущем примере, наряду со свойствами, относящимися к штрихам, о которых говорилось во время обсуждения фигур (StartLine, EndLineCap, DashStyle, DashCap, LineJoin и MiterLimit). Фактически большинство классов-наследников Shape внутренне применяют объекты Pen в своем коде рисования, но предлагают связанные с перьями свойства для непосредственного использования.

GeometryDrawing — не единственный класс для рисования в WPF (хотя он и наиболее важный, когда речь идет о двухмерной векторной графике). Фактически класс Drawing предназначен для того, чтобы представлять все типы двухмерной графики, и только небольшая группа классов наследуется от него. Все они перечислены ниже:

Классы, производные от Drawing
Класс Описание Свойства
GeometryDrawing Создает оболочку для геометрии с кистью, заполняющей ее, и пером, очерчивающим контур Geometry, Brush, Pen
ImageDrawing Создает оболочку для графического изображения (обычно растрового изображения из файла) с прямоугольником, определяющим его границы ImageSource, Rect
VideoDrawing Комбинирует MediaPlayer, используемый для воспроизведения видео, с прямоугольником, задающим его границы. Player, Rect
GlyphRunDrawing Создает оболочку для низкоуровневого текстового объекта, известного как GlyphRun, с рисующей его кистью GlyphRun, ForegroundBrush
DrawingGroup Комбинирует коллекцию объектов Drawing любого типа. DrawingGroup позволяет создавать составные рисунки и применять эффекты ко всей коллекции сразу, используя одно из его свойств BitmapEffect, BitmapEffectInput, Children, ClipGeometry, GuidelineSet, Opacity, OpacityMask, Transform

Отображение рисунка

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

DrawingImage

Позволяет разместить рисунок внутри элемента Image

DrawingBrush

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

DrawingVisual

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

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

Например, предположим, что необходимо воспользоваться частью векторного рисунка для создания пиктограммы кнопки. Наиболее удобный (и затратный в смысле ресурсов) способ сделать это — поместить Canvas внутрь кнопки, а затем поместить в Canvas набор элементов-наследников Shape. Как уже известно, если принять такой подход, то каждый элемент будет полностью независимым, с собственным пространством в памяти, обработкой событий и т.п.

Более удачный подход заключается в сокращении количества элементов за счет применения элемента Path. Поскольку каждый путь имеет единственный штрих и заполнение, понадобится множество объектов Path, но все же, скорее всего, удастся несколько сократить количество элементов.

Начав работать с элементом Path, вы переключаетесь от отдельных фигур к отдельным геометриям. Уровень абстракции можно поднять, если извлечь информацию о геометрии, штрихе и заполнении из пути и превратить ее в рисунок. Затем рисунки можно скомбинировать вместе в объект DrawingGroup и поместить этот объект в DrawingImage, а его, в свою очередь — в элемент Image.

Хотя DrawingImage обеспечивает большую экономию, все же можно увеличить эффективности и удалить один элемент с помощью DrawingBrush. Одним из продуктов, использующих данный подход, является Expression Blend.

Базовая идея заключается в помещении DrawingImage в DrawingBrush, примерно так:

<Button ... >
   <Button.Background>
      <DrawingBrush>
         <DrawingBrush.Drawing>
            <DrawingGroup>
              <GeometryDrawing ...>
              <GeometryDrawing ...>
              <GeometryDrawing ...>
            </DrawingGroup>
         </DrawingBrush.Drawing>
      </DrawingBrush>
   </Button.Background>
</Button>

Подход с DrawingBrush — это не то же самое, что подход с DrawingImage, показанный ранее, поскольку стандартные способы изменения размеров содержимого в Image отличаются. По умолчанию свойство Image.Stretch установлено в Uniform, что масштабирует изображение вверх и вниз для заполнения доступного пространства. По умолчанию свойство DrawingBrush.Stretch равно Fill, что может исказить изображение.

Одна причуда подхода на основе DrawingBrush заключается в том, что содержимое исчезает, когда курсор мыши перемещается над кнопкой и для рисования ее поверхности применяется новая кисть. Но когда используется подход на основе Image, рисунок остается нетронутым. Чтобы справиться с этой проблемой, нужно создать шаблон элемента управления для кнопки, который не будет перерисовывать фон таким же образом.

В любом случае, используется объект DrawingImage сам по себе или в оболочке DrawingBrush, стоит подумать об улучшении разметки с применением ресурсов. Основная идея состоит в определении каждого объекта DrawingImage или DrawingBrush в виде отдельного ресурса, на который затем можно ссылаться по мере необходимости. Это особенно хорошая мысль, если необходимо отобразить некоторое содержимое в более чем одном элементе или в более чем одном окне, потому что тогда можно просто повторно использовать ресурс, а не копировать целый блок разметки.

Пройди тесты
Лучший чат для C# программистов