Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Рисование в Swing

95

Программная перерисовка в Swing

Если вспомнить как мы рассматривали программную перерисовку в AWT (метод repaint()), то легко видеть что это по сути постановка события на перерисовку (PaintEvent) в очередь событий интерфейса. Именно эта простая идея подтолкнула создателей Swing включить оптимизацию и здесь, на этот раз применив принцип пакетной обработки.

Глядя на очередь, очевидно, что каждое событие из очереди выполняется некоторое время, и пока дело дойдет до нашего события на перерисовку PaintEvent, в очереди могут появиться новые события такого же типа. Весьма вероятно, что они относятся не только к тому же окну, в котором была запрошена перерисовка в первый раз, но и даже к одному и тому же компоненту, и возможно, последующие события делают первые и вовсе ненужными, потому что полностью зарисуют то, что нарисуют первые. Вообразите активную прокрутку большой таблицы — подобный процесс будет генерировать огромное количество вызовов метода repaint(), и следующие вызовы уже будут перерисовывать области, которые должны бы были нарисованы первыми вызовами. Определенно здесь можно оптимизировать процесс и избавиться от лишнего рисования.

Метод repaint() переопределен в классе JComponent и вместо прямой постановки события PaintEvent в очередь событий просит нашего старого знакомого RepaintManager добавить область, которую нужно перерисовать, в список «загрязненных» (dirty region). «Загрязненные» области нужно перерисовать, как только до них дойдут руки потока рассылки событий.

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

Следующая схема прекрасно иллюстрирует, что происходит и какие классы участвуют в процессе:

repaint()

RepaintManager.addDirtyRegion()

Список поврежденных областей

SystemEventQueueUtilities queueComponentWorkRequest()

postEvent()

collectDirtyRegions() paintDirtyRegions()

paintImmediately()

Поток EventDispatchThread

Прорисовка

Вспомогательный класс SystemEventQueueUtilities ставит то самое особое событие (его имя ComponentWorkRequest) в очередь, используя метод для постановки postEvent(). События PaintEvent в Swing вообще не применяются. Как только поток рассылки дохо-

96

ГЛАВА 4

дит до данного события, оно выполняется и вызывает метод все того же RepaintManager с названием paintDirtyRegions(). Опуская детали реализации, мы приходим к тому, что вызывается метод, определенный в базовом классе JComponent под названием paintImmediately(). Ну а в нем все уже совсем просто. В итоге методом getGraphics() создается графический объект и вызывается хорошо знакомый нам метод paint(). Работу этого метода в Swing мы подробно изучили чуть ранее, и все выполняется по той самой диаграмме, что мы составили. В результате компонент Swing перерисовывается, причем лишь один раз за определенный промежуток времени, и максимально оптимизировано.

Рисование в Swing: резюме

Подводя итоги всей системы рисования Swing, можно определить следующие самые важные выводы:

Метод paint() в Swing является частью внутренней системы и выполняет важную работу. Переопределять его для рисования компонента Swing не следует.

Для рисования компонента или просто графики применяется метод paint-

Component().

При рисовании в Swing необходимо учитывать свойство непрозрачности opaque. Если компонент непрозрачен, необходимо закрашивать его фон или не оставлять непрорисованных областей. Делается это либо вручную, либо вызовом super. paintComponent(), чтобы фон закрасил UI-представитель. Однако, второй способ не работает для компонентов, унаследованных напрямую от JComponent. Если не выполнить данное условие, мусор на экране неизбежен.

Перерисовка repaint() в Swing выполняется пакетами и оптимизируется. Если вас это не устраивает, придется переопределить или метод repaint(), или написать свой вариант класса RepaintManager. Впрочем, необходимости в этом практически не возникает.

Рисование «готовых» компонентов

Разбирая тонкости систем рисования библиотеки Swing и ее основы AWT, мы рассматривали все возникающие вопросы с точки зрения рисования с нуля, в пределах одного компонента, как это обычно и бывает. Однако легко себе представить и другую ситуацию — стандартные компоненты Swing весьма функциональны и полезны, и у вас может возникнуть желание «прорисовать» какой-либо из них в своем собственном компоненте. К примеру, надписи JLabel умеют отображать многострочный текст формата HTML и изображения, представьте, что вам пришлось бы заниматься тем же самым самостоятельно при создании любого нового компонента. Интересно, что сложные компоненты Swing, такие как таблицы JTable, используют те же самые надписи для отображения своих ячеек или текста, так что идея неплоха.

При работе с обычными легковесными компонентами AWT представить себе процесс их прорисовки в рамках другого компонента легко —нужно всего лишь вызвать метод paint(), предварительно задав размер компонента и возможно поменяв какие-то графические параметры объекта Graphics. Компонент никогда не знает, кто вызывает его прорисовку, и должен выполнять ее независимо от этого. Однако в Swing картина немного другая.

Если вспомнить процесс прорисовки компонентов Swing, то легко увидеть, что все действия идут через объект RepaintManager, а сам компонент, если он находится в другом компоненте, «подневолен» и настроен рисовать себя через буфер своего

Рисование в Swing

97

компонента-предка. Если мы создадим новый компонент Swing, не добавляя его на экран, он увидит, что предков у него нет, и решит, что является первым компонентом в иерархии компонентов, пытаясь нарисовать себя через объект RepaintManager и экранный буфер. Так как компонент на самом деле на экране не находится, то и содержимое буфера скопировать будет некуда и прорисовка не получится. В этом случае компоненты Swing приходится настраивать дополнительно: отключать для таких компонентов двойную буферизацию. Тогда они рисуют себя напрямую и появляются на экране.

Интересно, что в свежих версиях JDK отключение буферизации не требуется, так как она и так отключена и заменена на буферизацию системного уровня. Однако

втом случае, если системная буферизация будет недоступна, стоит отключать буферизацию компонентов Swing, которые мы хотим рисовать на экране, но не добавлять

вконтейнеры.

Рассмотрим небольшой пример с прорисовкой компонентов:

//PaintingOtherComponents.java

//Прорисовка других компонентов как изображений import javax.swing.*;

import java.awt.*;

public class PaintingOtherComponents extends JFrame { public PaintingOtherComponents() {

super("PaintingOtherComponents"); setDefaultCloseOperation(EXIT_ON_CLOSE); add(new CustomPaintComponent()); setSize(400, 300);

setVisible(true);

}

class CustomPaintComponent extends JPanel { // кнопка для прорисовки

private JButton button = new JButton("Привет!"); // метод для рисования в Swing

protected void paintComponent(Graphics g) {

//необходимо вызвать для обработки свойства opaque super.paintComponent(g);

//рисуем кнопки

Graphics2D g2 = (Graphics2D) g; button.setSize(80, 30);

//отключение двойной буферизации — не всегда нужно button.setDoubleBuffered(false);

//переместим позицию рисования

g2.translate(100, 100); for (int i=1; i<=8; i++) {

// кручение кнопки по кругу g2.rotate(2*Math.PI / i);

98

ГЛАВА 4

button.paint(g);

}

}

}

public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() {

public void run() { new PaintingOtherComponents(); } });

}

}

В примере мы наследуем от окна с рамкой JFrame (подробности об окнах вы всегда сможете узнать в главе 6) и добавляем в его центр свой компонент Swing, унаследованный от панели JPanel. Если вспомнить все, что мы говорили о рисовании в Swing, то для рисования нам необходимо переопределить метод paintComponent(), вызвать метод родительского класса, чтобы тот позаботился о свойстве непрозрачности, а дальше делать

сграфическим объектом Graphics все, на что хватит фантазии.

Впримере мы будем рисовать кнопку JButton с помощью стандартных средств AWT для рисования, поворачивая ее вокруг своей начальной точки на полный круг. Для этого предназначен метод rotate(), которому необходимо задать угол поворота в радианах. Кнопка создается одна, а нарисовать ее на экране можно бесчисленное количество раз. Нужно лишь не забыть задать ей корректный размер методом setSize() и, настроив по вкусу свойства рисования в объекте Graphics, вызвать метод paint(). Обратите вни-

мание, что сама кнопка понятия не имеет о том, что ее «крутят», за все отвечает объект Graphics. Как правило, отключение двойной буферизации в свежих версиях JDK не требуется, вы можете убрать его и запустить пример заново в качестве простого домашнего упражнения.

Таким образом, все стандартные компоненты Swing к вашим услугам, если вы захотите нарисовать их в виде статичных изображений в своем компоненте. Интересно, что в Swing есть и другой, еще более эффективный способ рисовать компоненты. Это вспомогательный контейнер CellRendererPane, в который можно добавить компонент,

Рисование в Swing

99

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

Рисование вне рамок

Рассматривая систему рисования Swing, мы постоянно говорим об одном компоненте и рисовании в его пределах. Тем не менее, зачастую приходится задумываться о рисовании над многими компонентами сразу, а то и над всем интерфейсом приложения. Особенно это верно для насыщенных графикой и анимацией интерактивных пользовательских интерфейсов. Классический подход для таких задач не слишком подходит: мы увидели, что система рисования Swing тщательно оптимизирована, а переопределять рисующий метод главного окна приложения чревато нарушениями в стандартной системе и сложностями реализации.

Специального решения на этот случай в Swing не предусмотрено, однако мы можем воспользоваться побочными эффектами реализации библиотеки, применяя особые компоненты, такие как многослойные (layered pane) и прозрачные панели (glass pane), существующие в каждом окне Swing-приложений или апплете. В главе 6 мы рассмотрим их вместе со способами применения для создания эффектной графики .

RepaintManager как прикладной инструмент

Подводя итоги рассмотрения механизмов рисования Swing, мы видим, какую серьезную роль играет в них класс RepaintManager. Весь процесс рисования компонентов Swing, благодаря особой реализации базового класса JComponent, проходит через его двойную буферизацию, он также полностью заведует программной перерисовкой компонентов, оптимизируя ее. С этой точки зрения он всего лишь внутренний «винтик» Swing, однако с учитом того, что все «нити» при рисовании сходятся в одно место, мы получаем уникальную возможность вмешаться в рабочий процесс рисования, так как можем заменить экземпляр класса RepaintManager на свой собственный, методом setCurrentManager().

Прежде чем задуматься о собственной реализации, важно узнать, какие дополнительные реализации этого класса уже существуют. Прежде всего, это инструменты, которые мы обсуждали в предыдущей главе, для слежения за тем, чтобы все рисование в Swing велось из потока рассылки событий. Обращение к экземпляру класса RepaintManager из другого потока сразу же обозначает ошибку программиста и необходимость вынести вызывающий код в поток рассылки событий. Настолько же интересно применяет свой экземпляр класса RepaintManager библиотека вспомогательных инструментов SwingX. В ней существует панель, позволяющая настроить уровень прозрачности, как свой, так и всех содержащихся в ней компонентов. Однако, как мы знаем, при необходимости компоненты сами перерисовывают друг друга, и со стороны никак нельзя «заставить» их рисовать себя полупрозрачными. Решением является особый объект RepaintManager, который при запросе на перерисовку компонентов, находящихся в прозрачной панели, направляет их самой полупрозрачной панели так, чтобы она смогла настроить все параметры рисования и только потом нарисовать потомков.

Таким образом, можно использовать свой объект RepaintManager для контроля каких-либо критичных параметров в процессе рисования или же определять, какой

Источник: https://studfile.net/preview/15935977/