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

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

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

Контейнеры высшего уровня

125

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

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

0

 

 

 

 

 

 

0

 

1

 

 

 

0

2

(n-1)

 

 

 

 

 

 

 

 

 

 

n – количество компонентов в слое

Рис. 6.2. Позиции компонентов в слое

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

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

126

ГЛАВА 6

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

Чтобы лучше понять, как многослойная панель используется во внутренних механизмах Swing, а затем правильно применять ее в собственных целях, мы рассмотрим стандартные слои, характерные для всех компонентов Swing.

Как мы уже знаем, многослойная панель позволяет организовать столько слоев, сколько целых чисел вы сможете придумать (доступны даже отрицательные целые числа). Тем не менее, компоненты, использующие многослойную панель, должны быть уверены в том, что при добавлении компонента в определенный слой не возникнет путаницы, и компоненты, которые должны находиться выше других в стопке, не окажутся закрытыми из-за того, что кто-то по незнанию занял более высокий слой. Чтобы избежать путаницы, многослойная панель определяет несколько стандартных слоев, которые и используются всеми компонентами Swing, что позволяет обеспечить правильную работу всех механизмов многослойной панели. Посмотрим теперь, какие стандартные слои имеются в Swing (рис. 6.3).

DEFAULT (0)

PALETTE(100)

MODAL(200)

POPUP(300)

DRAG(400)

Рис. 6.3. Стандартные слои Swing

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

Таблица 6.1. Предназначение стандартных слоев многослойной панели

Название слоя

Предназначение

 

 

Default

Этот слой используется для всех обычных компонентов, которые вы добавляете

 

в контейнер. В нем же располагаются внутренние окна многодокументных при-

 

ложений

Palette

Слой предназначен для размещения так называемых палитр, или окон с набором

 

инструментов, которые обычно перекрывают остальные элементы интерфейса.

 

Создавать такие окна позволяет панель JDesktopPane, которая размещает их как

 

раз в этом слое

Modal

Судя по названию, разработчики планировали использовать этот слой для раз-

 

мещения легковесных модальных диалоговых окон. Однако такие диалоговые

 

окна пока не реализованы, так что этот слой в Swing в настоящее время не ис-

 

пользуется

Контейнеры высшего уровня

127

Таблица 6.1 (продолжение)

Название слоя Предназначение

Popup

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

 

всплывающих меню и подсказок

Drag

Самый верхний в стопке слой. Предназначен для обслуживания операций

 

перетаскивания (drag and drop), которые должны быть хорошо видны поль-

 

зователю

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

//LayeredTest.java

//Возможности многослойной панели import javax.swing.*;

import java.awt.*;

public class LayeredTest extends JFrame { public LayeredTest() {

super("LayeredTest");

//выход при закрытии окна setDefaultCloseOperation(EXIT_ON_CLOSE);

//получаем многослойную панель

JLayeredPane lp = getLayeredPane();

//три фигуры

Figure fg1 = new Figure(Color.red, 0, "POPUP"); Figure fg2 = new Figure(Color.blue, 0, "PALETTE1"); Figure fg3 = new Figure(Color.green, 1, "PALETTE2");

//расположение фигур в окне fg1.setBounds(10, 10, 120, 120); fg2.setBounds(60, 80, 160, 180); fg3.setBounds(90, 15, 250, 180);

//добавляем в различные слои lp.add(fg1, JLayeredPane.POPUP_LAYER); lp.add(fg2, JLayeredPane.PALETTE_LAYER); lp.add(fg3, JLayeredPane.PALETTE_LAYER);

//смена позиции одной из фигур lp.setPosition(fg3, 0);

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

setSize(300, 200); setVisible(true);

}

// класс, позволяющий рисовать два типа фигур с текстом

128 ГЛАВА 6

class Figure extends JComponent { private Color color;

private int type; private String text;

// параметры: цвет и тип фигуры

Figure(Color color, int type, String text) { this.color = color;

this.type = type; this.text = text; setOpaque(false);

}

public void paintComponent(Graphics g) { // прорисовка фигуры g.setColor(color);

switch (type) {

case 0: g.fillOval(0, 0, 90, 90); break; case 1: g.fillRect(0, 0, 130, 80); break;

}

g.setColor(Color.white); g.drawString(text, 10, 35);

}

}

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

new Runnable() {

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

}

}

В данном примере мы создаем небольшое окно JFrame, указываем ему, что при закрытииокнанеобходимозавершитьпрограмму(спомощьюметодаsetDefaultCloseOperation(), о котором мы вскоре узнаем подробнее), и добавляем в многослойную панель несколько компонентов Figure. Чтобы получить многослойную панель в любом контейнере высшего уровня Swing, достаточно вызвать метод getLayeredPane(). Вспомогательный класс Figure наследует от базового класса JComponent и позволяет различными цветами рисовать фигуры двух типов (круги и прямоугольники), вводя поверх фигур надписи. Параметры для прорисовки фигур задаются в конструкторе класса. Заметьте, что компоненты Figure не закрашивают цвет фона (свойство непрозрачности установлено в false, для этого мы в конструкторе воспользовались методом setOpaque()), это позволяет им лучше «просвечивать» друг сквозь друга. Здесь мы создаем три фигуры разного цвета (два круга и прямоугольник) и добавляем круг в слой POPUP_LAYER, а прямоугольники — в слой PALETTE_LAYER. Заметьте, что при добавлении компонентов приходится указывать их абсолютные экранные координаты, потому что в многослойной панели обычные менеджеры расположения не работают. В нашем примере мы для простоты сразу указали координаты фигур, однако в реальных приложениях лучше

Контейнеры высшего уровня

129

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

Вобычных приложениях многослойная панель редко используется напрямую,

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

Панель содержимого

Панель содержимого (content pane) — следующая часть корневой панели, служащая для размещения компонентов пользовательского интерфейса вашей программы. Она занимает большую часть пространства многослойной панели (за исключением того места, что занимает строка меню). Чтобы панель содержимого не закрывала добавляемые впоследствии в окно компоненты, многослойная панель размещает ее в специальном очень низком слое с названием FRAME_CONTENT_LAYER, с номером —30000. Обычные компоненты размещать в этом слое не стоит, потому что тогда вы их вряд ли увидите, а для панели содержимого этот слой подходит в самый раз, потому что она служит всего лишь для размещения других компонентов (при добавлении в панель содержимого все компоненты оказываются в слое DEFAULT_LAYER).

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

getContentPane().add(ваш_компонент)

Впрочем, панель содержимого — это всего лишь экземпляр обычной панели JPanel, в которой для совместимости с окнами AWT устанавливается полярное расположение BorderLayout, поскольку считается, что в окнах по умолчанию должно использоваться полярное расположение (мы увидим в главе 7, что это действительно удобно). Никто не

130

ГЛАВА 6

запрещает поместить все ваши компоненты в отдельную панель с удобным вам расположением, а затем сделать ее панелью содержимого вашего окна. Это дело вкуса. Рассмотрим небольшой пример:

//ContentPaneAdd.java

//Замена панели содержимого import javax.swing.*;

import java.awt.*;

public class ContentPaneAdd extends JFrame { public ContentPaneAdd() {

super("ContentPaneAdd"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//создадим панель с двумя кнопками

JPanel contents = new JPanel(); contents.add(new JButton("Один")); contents.add(new JButton("Два"));

//заменим панель содержимого setContentPane(contents);

//выведем окно на экран setSize(200, 100); setVisible(true);

}

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

new Runnable() {

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

}

}

В примере мы создаем небольшое окно и панель с двумя кнопками, которую затем методом setContentPane() делаем панелью содержимого нашего окна. Так мы вместо полярного расположения задействуем предлагаемый по умолчанию в панелях JPanel менеджер FlowLayout. Тем не менее, разница невелика и выбор того или иного подхода зависит от ваших пристрастий. В общем, панель содержимого не представляет собой ничего хитрого, надо лишь помнить, что компоненты добавляются именно в нее.

Строка меню

Одной из самых важных причин использования корневой панели в Swing является необходимость размещения в окне строки меню (menu bar). Более или менее сложное приложение вряд ли сможет обойтись без системы меню, позволяющей пользователю легко получить доступ ко всем функциям приложения, которые к тому же логически организованы в группы. Библиотека Swing (подробнее мы узнаем об этом в главе 10) предоставляет прекрасные возможности для создания больших и удобных систем меню, и, само собой, строка меню JMenuBar также является легковесным компонентом.

Контейнеры высшего уровня

131

Однако стандартные окна AWT, напрямую связанные с операционной системой, могут размещать только строки меню AWT и не знают о легковесных меню Swing. Здесь нам помогает корневая панель. Она выделяет под строку меню специальное место и предоставляет метод setJMenuBar(), позволяющий вам установить в контейнере новую строку меню2 (строка меню может использоваться в окне JFrame, диалоговом окне JDialog и апплете JApplet).

Строка меню также размещается в многослойной панели, в том же специальном слое FRAME_CONTENT_LAYER, и занимает небольшое пространство в верхней части окна, в длину равное размеру окна, ширина используемого пространства при этом зависит от самой строки меню и содержащихся в ней элементов. Корневая панель следит за тем, чтобы панель содержимого и строка меню не перекрывались. Если строка меню в контейнере высшего уровня не требуется, корневая панель использует все пространство окна для размещения панели содержимого. В принципе, это все, что можно сказать о поддержке корневой панелью строки меню; здесь все очень просто.

Прозрачная панель

Прозрачная панель (glass pane) — это последний составляющий элемент корневой панели. Прозрачная панель размещается корневой панелью выше всех элементов многослойной панели — как бы высоко не находился используемый вами слой, прозрачная панель всегда будет выше. Следит за выполнением этого правила корневая панель, которая размещает прозрачную панель выше многослойной панели, причем так, чтобы она полностью закрывала всю область окна, включая и область, занятую строкой меню.

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

видимой, нужно быть уверенным в том, что она прозрачна (ее свойство непрозрачности opaque равно false), потому что в противном случае она закроет все остальные

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

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

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

132

ГЛАВА 6

пользователь сможет эффективно им пользоваться. Попробуем реализовать такое поведение с помощью прозрачной панели:

//HelpSystemDemo.java

//Как прозрачная панель может помочь в создании

//системы помощи

import javax.swing.*; import java.awt.*; import java.awt.event.*;

public class HelpSystemDemo extends JFrame { // необходимые нам поля

private JButton button1, button2, help; private HelpSystem hs = new HelpSystem(); private InterceptPane ip = new InterceptPane();

private ImageIcon helpIcon = new ImageIcon("Help.gif");

public HelpSystemDemo() { super("HelpSystemDemo"); setDefaultCloseOperation(EXIT_ON_CLOSE); // создаем нащ интерфейс

button1 = new JButton("Что-то делает"); button2 = new JButton("Тоже что-то делает"); JPanel contents = new JPanel(); contents.add(button1); contents.add(button2);

// кнопка вызова помощи

help = new JButton(helpIcon); help.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

//при нажатии включаем прозрачную панель ip.setVisible(true);

//и специальный указатель мыши getRootPane().setCursor(getToolkit().

createCustomCursor(helpIcon.getImage(), new Point(0, 0), ""));

}

});

contents.add(help);

// настраиваем наш интерфейс и прозрачную панель setContentPane(contents);

setGlassPane(ip);

Контейнеры высшего уровня

133

// выводим окно на экран setSize(200, 200); setVisible(true);

}

// компонент, перехватывающий события class InterceptPane extends JComponent {

InterceptPane() {

//надо включить события от мыши enableEvents(MouseEvent.MOUSE_EVENT_MASK); enableEvents(KeyEvent.KEY_EVENT_MASK);

//по умолчанию невидим и прозрачен setVisible(false); setOpaque(false);

}

// перехватываем события от мыши

public void processMouseEvent(MouseEvent e) { // отслеживаем нажатия мыши

if ( e.getID() == MouseEvent.MOUSE_PRESSED) {

//определяем, какой компонент был выбран

Component contentPane = getContentPane(); MouseEvent ne =

SwingUtilities.convertMouseEvent( this, e, contentPane);

//видимый компонент в указанных координатах

Component visibleComp = SwingUtilities.getDeepestComponentAt(

contentPane, ne.getX(), ne.getY());

//показываем справочную информацию

JOptionPane.showMessageDialog(

null, hs.getHelpFor(visibleComp));

//отключаемся

setVisible(false);

// возвращаем на место обычный указатель мыши getRootPane().setCursor(

Cursor.getDefaultCursor());

}

}

}

// прототип системы помощи class HelpSystem {

// получает помощь для компонентов

134

ГЛАВА 6

 

public String getHelpFor(Component comp) {

 

if ( comp == button1)

 

return "Останавливает реактор. Лучше не жмите";

 

else if ( comp == button2 )

 

return "Хотите лимонада? Тогда жмите смело!";

 

return "Даже и не знаю, что это такое";

 

}

 

}

 

public static void main(String[] args) {

 

SwingUtilities.invokeLater(

 

new Runnable() {

 

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

}

}

 

В данном примере мы создаем небольшое окно с тремя кнопками, две из которых представляют собой обычные элементы пользовательского интерфейса (здесь для простоты они ничего не делают), а третья, со специальным значком3 вместо надписи, включает режим получения помощи, в котором элементы интерфейса перестают реагировать на нажатие кнопок мыши, вместо них манипуляции кнопками контролирует прозрачная панель, вызывая для выбранного компонента систему помощи. Чтобы реализовать подобное поведение, нам потребовалось несколько этапов. Во-первых, мы создали специальный компонент InterceptPane, унаследовав его от базового класса JComponent. Заметьте, что мы сразу же указываем в конструкторе, что наш компонент является прозрачным (невидимым) — свойство непрозрачности меняется на false (как было отмечено в главе 4, по умолчанию оно равно true). Интересно, что здесь же, в конструкторе нашего компонента, нам приходится вручную включать режим получения им сообщений от мыши (методом enableEvents()). Мы не добавляем слушателей, переопределяя вместо этого метод processMouseEvent(), что в нашем случае удобнее (это позволяет получить доступ сразу ко всем событиям от мыши), и поэтому события не включаются автоматически (маскирование событий мы обсуждали в главе 3). Компонент InterceptPane будет использоваться как прозрачная панель нашего приложения, и в его методе processMouseEvent() мы будем перехватывать нажатия кнопок мыши, вызывая при этом помощь для компонента, в области которого было зафиксировано нажатие кнопки. Роль справочной системы играет внутренний класс HelpSystem, который в нашем примере просто возвращает для каждого компонента небольшое сообщение, а в реальности может представлять собой полнофункциональную систему помощи.

В итоге мы получаем следующую картину: после запуска приложения прозрачная панель (представленная экземпляром класса InterceptPane) невидима и не перехватывает событий от мыши (вы можете свободно щелкать на первых двух кнопках). Если же вы щелкните на третьей кнопке, которая делает прозрачную панель видимой (а заодно заменяет указатель мыши более подходящим, напоминающим значок, который находится на кнопке), последняя начинает перехватывать нажатия кнопок мыши. После этого попытка щелчка мышью на любой из первых двух кнопок вызывает систему помощи, однако сами кнопки при этом и не подумают пошевелиться, потому что событие до них не доходит. Для этого в методе

3 Этот значок взят из стандартного набора графики Java Look And Feel Graphics Repository, распространяемого сайтом java.sun.com и созданного специально для внешнего вида Metal.