Добавил:
СПбГУТ * ИКСС * Программная инженерия Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Портянкин И. Swing

.pdf
Скачиваний:
140
Добавлен:
07.10.2020
Размер:
4.63 Mб
Скачать

Рисование в 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().

90

ГЛАВА 4

Кэширование

Стратегия кэширования, применяемая в Swing, крайне проста и стара как мир. Если устройство выводит данные на экран слишком медленно (не то чтобы сейчас медленные видеокарты, но помните что в Java это вызовы «родного» кода), необходимо заранее подготовить все в быстром хранилище-буфере (в памяти), а затем одним махом (одной операцией) записать все в устройство (на экран). В качестве буфера применяется область в памяти в формате экрана, по возможности — область

впамяти видеокарты. Буфер этот хранит вспомогательный класс для рисования

вSwing под названием RepaintManager. Он же проверяет, что буфер корректен, при

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

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

1.Операционная система просит окно заново нарисовать содержимое

2.Окно проходит по списку содержавшихся в нем легковесных компонентов и находит там компонент Swing

3.Вызывается метод paint(). Чтобы встроить оптимизацию во все компоненты

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

4.Если (согласно флагам), это первый вызванный для рисования компонент Swing, то он запрашивает рисование через буфер у класса RepaintManager. Тот настраивает буфер и вызывает метод paintToOffscreen() вызвавшего его компонента, передавая ему объект Graphics для рисования в памяти. Выставляется флаг, который отныне говорит, что рисование пошло в буфер.

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

6.Компоненты-потомки рисуют своих потомков (напрямую, согласно флагам), и так далее до полной прорисовки всей иерархии данного компонента Swing.

7.После окончания этой процедуры управление (согласно шагу 4) возвращается в RepaintManager, которые копирует изображение компонентов из буфера на реальный экран.

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

Кэширование (буферизацию) графики в Swing можно отключить либо методом

RepaintManager.setDoubleBufferingEnabled(), либо непосредственно на компоненте методом

Рисование в Swing

91

setDoubleBuffered(). Правда, смысл выключения двойной буферизации для отдельных компонентов немного меняется. Мы уже видели, что компонент рисует с помощью буфера, если буфер используется его родительским компонентом, независимо от того, включена или выключена двойная буферизация для него самого. Если все компоненты находятся в корневой панели, выключать двойную буферизацию имеет смысл только для нее (это будет равносильно выключению двойной буферизации для всех компонентов, находящихся внутри этого контейнера высшего уровня). Учтите, что с появлением в AWT, начиная с версий Java 1.4, системной поддержки буферизации, система Swing может быть отключена во избежание дублирования. С другой стороны, отключать оптимизацию самостоятельно приходится редко, один случай мы вскоре рассмотрим, а вообще лучше довериться Swing.

Разделение обязанностей

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

Рисование компонента в Swing, в отличие от простой до невозможности процедуры в библиотеке AWT, разбито на три этапа, и за каждый отвечает свой метод в классе JComponent. Как раз эти методы вы будете переопределять, если вам захочется нарисовать по-своему что-либо на компоненте Swing.

Метод paintComponent()

Метод paintComponent() вызывается при прорисовке компонента первым, и именно он рисует сам компонент. Разница между ним и классическим методом paint(), используемым в AWT, состоит в том, что вам не нужно заботиться ни об оптимизации рисования, ни о правильной прорисовке своих компонентов-потомков. Обо всем этом позаботятся механизмы класса JComponent. Все, что вам нужно сделать, — нарисовать в этом методе компонент и оставить всю черновую работу базовому классу.

Как вы помните, в Swing используется немного модифицированная архитектура MVC, в которой отображение компонента и его управление выполняются одним элементом, называемым UI-представителем. Оказывается, что прорисовка компонента с помощью UI-представителя осуществляется именно из метода paintComponent(), определенного в базовом классе JComponent. Действует метод очень просто: определяет, есть ли у компонента UI-представитель (не равен ли он пустой ссылке null) и, если представитель есть, вызывает его метод update(). Метод update() для всех UIпредставителей работает одинаково: по свойству непрозрачности проверяет, нужно ли закрашивать всю свою область цветом фона, и вызывает метод paint(), определенный в базовом классе всех UI-представителей — классе ComponentUI. Последний метод и рисует компонент. Остается лишь один вопрос: что такое свойство непрозрачности?

Мы отмечали, что одним из самых впечатляющих свойств легковесных компонентов является их способность быть прозрачными. Однако при написании библиотеки

92

ГЛАВА 4

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

Если в AWT любой легковесный компонент автоматически считается прозрачным, то в Swing все сделано наоборот. Свойство непрозрачности определяет, обязуется ли компонент закрашивать всю свою область, чтобы избавить Swing от дополнительной работы по поиску и прорисовке всего того, что находится под компонентом. Если свойство непрозрачности равно true (а по умолчанию оно равно true), то компонент обязан закрашивать всю свою область, иначе на экране вместо него появится мусор. Дополнительной работы здесь немного: всего лишь необходимо зарисовать всю свою область, а облегчение для механизмов прорисовки получается значительное. Ну а если вы всетаки решите создать компонент произвольной формы или прозрачный, вызовите для него метод setOpaque(false), и к вам снова вернутся все чудесные возможности легковесных компонентов — система прорисовки будет предупреждена. Однако злоупотреблять этим не стоит: скорость прорисовки такого компонента значительно падает. Во многом из-за этого в Swing не так уж и много компонентов, имеющих прозрачные области.

Вернемся к методу paintComponent(). Теперь роль его вполне очевидна: он прорисовывает компонент, по умолчанию используя для этого ассоциированного с компонентом UI-представителя. Если вы собираетесь создать новый компонент с собственным UI-представителем, то он будет прекрасно вписываться в эту схему. Унаследуйте своего UI-представителя от базового класса ComponentUI и переопределите метод paint()4, в котором и рисуйте компонент. Базовый класс позаботится о свойстве непрозрачности. Если же вам просто нужно что-либо нарисовать, унаследуйте свой компонент от любого подходящего вам класса (лучше всех для этого подходят непосредственно классы JComponent или JPanel, потому что сами они ничего не рисуют) и переопределите метод paintComponent(), в котором и рисуйте. Правда, при таком подходе нужно позаботиться о свойстве непрозрачности (если оно равно true) самостоятельно: потребуется закрашивать всю область прорисовки или вызывать перед рисованием базовую версию метода super.paintComponent()5.

Метод paintBorder()

Благодаря методу paintBorder() в Swing имеется такая замечательная вещь, как рамка (border). Для любого компонента Swing вы можете установить рамку, используя метод setBorder(). Оказывается, что поддержка рамок целиком и полностью обеспечи-

4 С названиями рисующих методов в Swing сплошная путаница: все они называются одинаково. Здесь речь идет конечно о методе paint(), определенном в классе ComponentUI, а не об основном рисующем методе любого компонента. Видимо, создатели Swing хотели наглядно показать, что UIпредставитель буквально забирает часть методов у компонента.

5 Однако нужно помнить, что вызов базового метода правильно сработает только для панели (JPanel), у которой есть свой UI-представитель, способный обработать свойство непрозрачности. У класса JComponent такого представителя нет (он абстрактный, вы не сможете вывести его на экран, так что UI-представитель ему на самом деле не нужен), и если вы наследуете от него, то заботьтесь о свойстве непрозрачности самостоятельно.

Рисование в Swing

93

вается методом paintBorder() класса JComponent. Он вызывается вторым, после метода paintComponent(), смотрит, установлена ли для компонента какая-либо рамка, и если рамка имеется, прорисовывает ее, вызывая определенный в интерфейсе Border метод paintBorder(). Единственный вопрос, который при этом возникает: где именно рисуется рамка? Прямо на пространстве компонента или для нее выделяется отдельное место? Ответ прост — никакого специального места для рамки нет. Она рисуется прямо поверх компонента после прорисовки последнего. Так что при рисовании компонента, если вы не хотите неожиданного наложения рамки на занятое место, учитывайте место, которое она занимает. Как это делается, мы узнаем в главе 8, часть которой полностью посвящена рамкам.

Переопределять метод paintBorder() вряд ли стоит. Работу он выполняет нехитрую, и как-либо улучшить ее или коренным образом изменить не представляется возможным. Если вам нужно создать для своего компонента фантасмагорическую рамку, лучше воспользоваться услугами интерфейса Border или совместить несколько стандартных рамок.

Метод paintChildren()

Заключительную часть процесса рисования выполняет метод paintChildren(). Как вы помните, при обсуждении легковесных компонентов в AWT мы отмечали, что для их правильного отображения в контейнере, если вы переопределили их методы paint(), необходимо вызвать базовую версию paint() из класса Container, иначе легковесные компоненты на экране не появятся. Базовый класс JComponent библиотеки Swing унаследован от класса Container и вполне мог бы воспользоваться его услугами по прорисовке содержащихся в нем компонентов-потомков. Однако создатели Swing решили от услуг класса Container отказаться и реализовали собственный механизм прорисовки потомков. Причина проста — по сравнению с AWT компоненты Swing намного сложнее и требуют иного подхода. Улучшенный оптимизированный механизм и реализуется методом paintChildren(). Для придания ему максимальной скорости компоненты Swing используют два свойства: уже известное нам свойство непрозрачности opaque, а также свойство isOptimizedDrawingEnabled.

Метод paintChildren() действует по алгоритму, который был слегка вольнодумно назван нами «с глаз долой, из сердца вон». Он получает список содержащихся в компоненте потомков и начинает перебирать их, используя при этом текущий прямоугольник отсечения. Основной задачей его является нахождение «слепых зон», то есть зон, где компоненты закрываются друг другом. Если компонент закрыт или вообще не попадает в область отсечения, то зачем его рисовать — никто труда не заметит. Можно нарисовать только тот компонент, что виден сверху.

На этом этапе «вступают в бой» два вышеупомянутых свойства. С первым свойством все более или менее понятно: если свойство непрозрачности компонента равно true, это означает, что, сколько бы компонентов не находилось под ним и не пересекало бы его, он обязуется закрасить всю свою область, а значит продолжать поиск потомков в этой области не имеет смысла — их все равно не будет видно. Теперь понятно, почему так важно выполнять требование заполнения всей области экрана при использовании свойства непрозрачности: в противном случае на экране неизбежен мусор, который по договоренности должен убирать сам компонент, а не система прорисовки. Со свойством isOptimizedDrawingEnabled картина немного другая.

Данное свойство определено в классе JComponent как предназначенное только для чтения: вы не можете изменить его, кроме как унаследовав собственный компонент и переопределив метод isOptimizedDrawingEnabled(). По умолчанию для большинства компонентов свойство isOptimizedDrawingEnabled равно true. Это позволяет снизить загрузку системы прорисовки. Проще говоря, это свойство гарантирует, что из под одними по-

94

ГЛАВА 4

томками не «просвечивают» другие и не задевают их, при этом на них не наложен дополнительный прозрачный компонент и т. д. Когда речь идет о простых компонентах, это свойство приносит небольшие дивиденды, однако, если у вас есть сложные и медленно рисующиеся компоненты, оно значительно ускоряет процесс. Когда свойство isOptimizedDrawingEnabled равно true, метод paintChildren() просто перебирает потомков и перерисовывает их поврежденные части, не разбираясь, что они собой представляют и как друг с другом соотносятся. Данное свойство переопределяют лишь три компонента: это многослойная панель JLayeredPane, рабочий стол JDesktopPane и область просмотра JViewport. В них компоненты-потомки часто перекрываются и требуют особого внимания.

Общая диаграмма рисования в Swing

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

Компонент:JComponent RepaintManager.currentManager()

paint()

1

beginPaint()

2

paint()

paintToOffscren()

paintComponent()

paintBorder()

paintChildren()

endPaint()

Вывод на экран

Как мы выяснили, в методе paint() компонентов Swing есть два пути — первый работает в том случае, если компонент Swing рисуется кем-то, не являющимся компонентом Swing, — в этом случае включаются процессы RepaintManager и подготавливается буферкэш. Если же они включены, идет стандартный второй путь, рисующий все известными уже нам тремя методами. И тот же самый второй способ вызывается напрямую, если буферизация в компоненте отключена (вручную или если буферизация включена на уровне операционной системы).