Класс Visual

79

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

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

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

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

Рисование объектов Visual

Visual — это абстрактный класс, так что создавать его экземпляры нельзя. Вместо этого понадобится использовать один из его классов-наследников. К ним относится UIElement (корень модели элементов WPF), Viewport3DVisual (позволяющий отображать трехмерное содержимое) и ContainerVisual (являющийся базовым контейнером, содержащим остальные объекты Visual). Но наиболее полезный класс-наследник — это DrawingVisual, унаследованный от ContainerVisual и добавляющий поддержку, необходимую для "рисования" графического содержимого, которое требуется поместить в визуальный объект.

Чтобы нарисовать содержимое в DrawingVisual, вызывается метод DrawingVisual.RenderOpen(). Этот метод возвращает DrawingContext, который можно использовать для определения содержимого визуального объекта. Завершив рисование, следует вызвать DrawingContext.Close(). Вот как это выглядит:

DrawingVisual visual = new DrawingVisual();
DrawingContext dc = visual.RenderOpen();
// Здесь выполняется рисование
dc.Close();

По сути, класс DrawingContext состоит из методов, которые добавляют некоторые графические детали к вашему визуальному объекту. Эти методы вызываются для рисования различных фигур, применения трансформаций, изменения прозрачности и т.п.

В таблице перечислены методы класса DrawingContext:

Методы класса DrawingContext
DrawLine(), DrawRectangle(), DrawRoundedRectangle() и DrawEllipse() Рисует указанную фигуру в указанной точке, с заданным заполнением и контуром. Эти методы отражают фигуры.
DrawGeometry() и DrawDrawing() Рисует более сложные объекты Geometry и Drawing
DrawText() Рисует текст в указанном месте. Передачей объекта FormattedText этому методу указывается текст, шрифт, заполнение и прочие детали. DrawText() можно использовать для рисования текста с переносами, если установить свойство FormattedText.MaxTextWidth
DrawImage() Рисует растровое изображение в указанной области (определенной в Rect)
DrawVideo() Отображает видеосодержимое (помещенное в объект-оболочку MediaPlayer) в определенной области.
Pop() Отменяет действие последнего вызова метода PushXxx(). Метод PushXxx() используется для временного применения одного или более эффектов, а метод Pop() — для его отмены
PushClip() Ограничивает рисование определенной областью. Содержимое, выходящее за его пределы, не рисуется
PushEffect() Применяет BitmapEffect к последующим операциям рисования
PushOpacity() и PushOpacityMask() Применяет новые установки прозрачности или маску прозрачности, чтобы сделать последующие операции рисования частично прозрачными
PushTransform() Устанавливает объект Transform, который будет применен к последующим операциям рисования. Трансформации могут быть использованы для масштабирования, перемещения, поворота или скоса содержимого

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

DrawingVisual visual = new DrawingVisual();
 using (DrawingContext dc = visual.RenderOpen())
 {
     Pen drawingpen = new Pen(Brushes.Black, 3);
     dc.DrawLine(drawingpen, new Point(0, 50), new Point(50, 0));
     dc.DrawLine(drawingpen, new Point(50, 0), new Point(100, 50));
     dc.DrawLine(drawingpen, new Point(0, 50), new Point(100, 50));
                
 }

Вызов методов DrawingContext на самом деле не приводит к рисованию визуального элемента — с их помощью просто определяется его внешний вид. По завершении операции вызовом Close() готовый рисунок помещается в визуальный элемент и предоставляется доступным только для чтения свойством DrawingVisual.Drawing. Среда WPF запоминает объект Drawing, так что при необходимости он может перерисовать окно.

Порядок кода рисования важен. Более поздние операции могут нарисовать содержимое поверх того, что нарисовано ранее. Методы PushXxx() задают настройки, которые будут применены к будущим операциям рисования. Например, PushOpacity() можно использовать для изменения уровня прозрачности, который затронет все последующие операции рисования. Метод Рор() может быть вызван для отмены действия последнего метода PushXxxO. В случае вызова более одного метода PushXxxO отключать их действие по одному можно последовательными вызовами Pop().

После закрытия DrawingContext модифицировать визуальный объект больше нельзя. Однако можно применить трансформацию или изменить общую прозрачность визуального объекта (с помощью свойств Transform и Opacity класса DrawingVisual). Чтобы установить полностью новое содержимое, понадобится снова вызвать RenderOpen() и повторить процесс рисования.

Многие методы рисования используют объекты Pen и Brush. Если вы планируете рисовать много визуальных объектов с одинаковым контуром и заполнением или собираетесь отображать один и тот же визуальный объект много раз (чтобы изменить его содержимое), стоит создать нужные объекты Pen и Brush заранее и хранить их на протяжении всего времени жизни окна.

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