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

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

Рисование в Swing

85

к легковесным компонентам. Как мы знаем, легковесный компонент представляет собой область экрана тяжеловесного контейнера. Для того чтобы он мог правильно отображаться на экране, ему также необходимо получать от системы прорисовки вызовы своего метода paint(), однако операционная система, заведующая этим процессом в AWT, ничего не знает о существовании легковесных компонентов, она видит лишь «родные» тяжеловесные компоненты и контейнеры, которым и отправляет запросы на перерисовку. Решение здесь очевидно — нужно встроить поддержку легковесных компонентов в тяжеловесные контейнеры, и именно так поступили создатели AWT.

Все контейнеры в AWT (а значит и в Swing) унаследованы от своего базового класса Container. Именно там легковесные компоненты и становятся полноправными участниками процесса вывода на экран. Никаких хитростей здесь нет, нужно лишь четко очертить круг участников этого процесса. Итак — легковесный компонент — это нечто, унаследованное от класса Component, и не связанное с операционной системой помощником (peer). Как он выглядит на экране, определяет исключительно его рисующий метод paint(). Эти компоненты можно добавлять в контейнер (любой, лишь бы он был унаследован от класса Container).

В контейнерах поддерживается иерархия компонентов, выстроенная «по оси Z» (z-order). Они как бы нанизываются на ось Z, устремленную от нас. Первый компонент имеет индекс 0, второй 1, и так далее. Рисование идет в обратном порядке, так что первый добавленный компонент всегда закрывает остальные, если они перекрывают друг друга. Диаграмма окончательно все прояснит:

N

Порядок добавления 2

1

0Порядок

вызова

paint()

Витоге, когда операционная система запрашивает прорисовку принадлежащего ей тяжеловесного контейнера (в случае приложения Swing это будет как правило окно JFrame либо диалог JDialog), вызывается его метод paint() (мы только что выяснили это

впредыдущем разделе). В нем, согласно порядку добавления компонентов, от последнего к первому, происходят следующие шаги:

Устанавливается область отсечения для рисования (clip), соответствующая области, занимаемой легковесным компонентов в контейнере.

Для прорисовываемого компонента в объекте Graphics выставляются текущие цвета фона и шрифта (берутся из контейнера, впрочем, компоненту совсем не обязательно их использовать).

Устанавливается текущий шрифт (из контейнера)

Настроенный объект Graphics передается в метод paint() легковесного компонента, который и рисует себя (а фактически, просто участвует в процессе прорисовки все того же тяжеловесного контейнера).

86

ГЛАВА 4

Идея проста — каждый легковесный компонент вносит свою лепту в процесс рисования контейнера, в то время как операционная система считает, что контейнер просто рисует себя. Так как установлена область отсечения, легковесный компонент никогда не сможет «залезть» в область, которая ему не принадлежит, и пририсовать там что-то лишнее. Поскольку рисование идет от последних добавленных компонентов к первым, первые всегда будут перекрывать последние в случае пересечения.

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

Долгое время из этой идиллии выпадали лишь тяжеловесные компоненты, если их добавляли в тот же контейнер. Вне зависимости от их позиции по оси Z, они перекрывали

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

во избежание проблем с прорисовкой и расположением на экране. Однако с появлением обновления Java версии 6, малой версии 12 и выше, и, конечно же, в Java 7 проблема была устранена и тяжеловесные компоненты перестали бессовестно «тянуть на себя одеяло» в контейнерах. Как правило, при разработке Swing-приложений совмещения компонентов практически не требовалось, но в случае необходимости задействовать какой-либо системный графический ресурс, например панель с ускорением трехмерной графики, возможность использовать вместе с такими тяжеловесными компонентами все богатство Swing бывает весьма кстати.

Атеперь рассмотрим простой пример и убедимся в справедливости всего сказанного:

//AWTLightweights.java

//Использование легковесных компонентов в AWT

import java.awt.*; import java.awt.event.*;

public class AWTLightweights extends Frame {

public AWTLightweights() { super("AWTLightweights");

//при закрытии окна приложение завершается addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) { System.exit(0);

}

});

//добавляем пару легковесных компонентов

LightweightRect rect1 =

new LightweightRect(Color.BLUE, true); LightweightRect rect2 =

new LightweightRect(Color.RED, true); LightweightRect transparentRect =

Рисование в Swing

87

new LightweightRect(Color.BLACK, false);

//укажем координаты вручную, чтобы компоненты

//перекрывались

setLayout(null); rect1.setBounds(40, 40, 100, 100); rect2.setBounds(50, 50, 100, 100);

transparentRect.setBounds(35, 35, 150, 150); add(transparentRect);

add(rect1);

add(rect2);

//последним добавляем тяжеловесный компонент

Button button = new Button("Тяжелая!"); button.setBounds(50, 175, 80, 30); add(button);

//выводим окно на экран

setSize(250, 250); setVisible(true);

}

// легковесный компонент — цветной прямоугольник class LightweightRect extends Component {

private Color color; private boolean fill;

// параметы — цвет и нужно ли зарисовывать всю область public LightweightRect(Color color, boolean fill) {

this.color = color; this.fill = fill;

}

public void paint(Graphics g) { g.setColor(color);

if (fill)

g.fillRect(0, 0, getWidth() — 1, getHeight() — 1); else

g.drawRect(0, 0, getWidth() — 1, getHeight() — 1);

}

}

public static void main(String[] args) { new AWTLightweights();

}

}

88

ГЛАВА 4

В примере в качестве тяжеловесного контейнера используется окно Frame, от которого мы наследуем. Легковесным компонентом выступит класс LightweightRect, который унаследован от базового класса Component и переопределяет метод для рисования paint(). Это прямоугольник, которому через конструктор можно задать цвет и свойство заполнения цветом — будет он закрашен или нет(нарисуется просто рамка).

Чтобы заставить компоненты перекрываться, придется отказаться от менеджера расположения (применяя метод setLayout(null) — подробнее о расположении компонентов рассказывается в главе 7), так как они не рассчитаны на перекрывание компонентов. Мы просто задаем позицию компонентов и их размеры методом setBounds(). Порядок добавления компонентов определяет их место и «первенство» на экране. Сначала идет незакрашенный прямоугольник, затем два закрашенных, и в последнюю очередь тяжеловесная кнопка Button. Несмотря на то, что прозрачный прямоугольник должен закрыть оба закрашенных, последние прекрасно видны — все благодаря прозрачности легковесных компонентов. Закрашенные прямоугольники перекрываются согласно старшинству — наверху тот, что был добавлен первым. Тяжеловесная кнопка находится в самом низу, так как была добавлена последней, то есть имеет самую дальнюю позицию по оси Z2. Однако прозрачность легковесного компонента на нее не действует, и вы легко это увидите, запустив пример. Причины кроются во внутренних механизмах библиотеки AWT, ну а нам придется аккуратнее совмещать тяжеловесные и легковесные компоненты, если это все же понадобится.

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

2 Если у вас есть инструмент, выводящий список окон для приложения (такой, как Spy++ для Windows), вы можете провести интересный эксперимент. Запустите программу с нашим примером и выведите ее список окон. Вы увидите, что их всего два — это само окно Frame и кнопка Button. Как мы и говорили, легковесных компонентов «родная» система не видит. С точки зрения Java это, тем не менее, самые настоящие компоненты.

Рисование в Swing

89

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

Легковесные компоненты: резюме

Легковесные компоненты не доставляют проблем, если помнить некоторые детали:

Легковесные компоненты хранятся в контейнере в порядке добавления (по оси Z), и рисуются наоборот — значит, те компоненты, что добавлены сначала, имеют на экране приоритет.

Легковесные компоненты могут быть прозрачны или рисовать себя в любой форме — через незатронутые области могут «просвечивать» другие компоненты или фон контейнера.

Метод repaint() работает для легковесных компонентов — но за кулисами перерисовывается часть тяжеловесного контейнера. Метод update() не вызывается.

Если вы переопределяете контейнер и его метод paint() и планируете затем добавлять в него другие компоненты, не забудьте вызвать базовый метод super. paint(), иначе о прорисовке легковесных компонентов придется позаботиться самостоятельно.

Система рисования Swing

По большому счету, легковесные компоненты ничем не ограничены, и создать с их помощью, не мудрствуя лукаво, даже такую богатую библиотеку, как Swing, не представляет собой технических трудностей. Однако во времена зарождения Swing пользовательские интерфейсы Java страдали от проблем с производительностью. Богатые возможностями, иногда даже прозрачные компоненты Swing просто сделали бы из пользовательского интерфейса мало удобоваримого тихохода. Действительно, все легковесные компоненты рисуются один за другим, вне зависимости от того, есть ли среди них непрозрачные области, и каждая операция с графикой — это долгая операция по обращения к «родному» коду операционной системы. Оптимизация была необходима как воздух, и оптимизация серьезная.

Отличие системы рисования Swing от стандартной состоит в оптимизации и поддержке UI-представителей. Другими словами, библиотека Swing в своей системе рисования руководствуется всего тремя принципами, которые позволяют нам избавиться от лишней работы и дают уверенность в том, что рисование максимально оптимизировано:

Кэшируй

Разделяй и властвуй

С глаз долой, из сердца вон

3Вспоминая, что при вызове repaint() сначала вызывается метод update(), мы видим, что

итут метод update() вызывается — но для тяжеловесного контейнера. А вот для легковесных компонентов update() не вызывается, так как рисующий их контейнер напрямую вызывает метод paint().

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