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

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

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

Искусство расположения

175

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

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

//SpringLayoutSample.java

//Работа менеджера SpringLayout import javax.swing.*;

import java.awt.*;

public class SpringLayoutSample extends JFrame { public SpringLayoutSample() {

super("SpringLayoutSample"); setDefaultCloseOperation(EXIT_ON_CLOSE);

//панель с использованием SpringLayout SpringLayout sl = new SpringLayout(); JPanel contents = new JPanel(sl);

//добавим пару компонентов

JButton button1, button2;

contents.add(button1 = new JButton("Первая")); contents.add(button2 = new JButton("Последняя"));

//настроим распорки sl.putConstraint(SpringLayout.WEST, button1,

5, SpringLayout.WEST, contents); sl.putConstraint(SpringLayout.WEST, button2, 5,

SpringLayout.EAST, button1);

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

}

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

new Runnable() {

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

}

}

Мы создаем небольшое окно JFrame, его панелью содержимого является панель JPanel, использующая менеджер расположения SpringLayout. В полученную панель добавляются две

176

ГЛАВА 7

кнопки. Для того чтобы они заняли свое место, необходимо задать для них нужные расстояния. Расстояние задается методом putConstraint() класса SpringLayout. В нем нужно указать границу компонента, сам компонент, расстояние, а также границу второго компонента и сам второй компонент (это тот компонент, что вы хотите отделить заданным расстоянием от первого компонента). Первый вызов putConstraint() отделяет западную (левую) границу первой кнопки от западной же границы контейнера. Второй вызов разделяет западную (левую) границу второй кнопки и восточную (правую) границу первой кнопки пятью пикселами. В итоге мы получаем две кнопки, расположенные горизонтально и отделенные пятью пикселами. Вы сможете легко в этом убедиться, запустив программу с примером.

Менеджер SpringLayout довольно мощен и гибок, но совершенно не приспособлен для создания интерфейса «вручную». Посмотрите: чтобы расположить компоненты, нужны все ссылки на них, приходится оперировать абсолютными значениями расстояний между ними, и создание не самого сложного расположения из десятка компонентов рискует превратиться в мешанину из вызовов putConstraint(). Поддерживать такой код очень сложно, и это не удивительно: менеджер SpringLayout предназначен, прежде всего, для автоматизированного построения интерфейса, а для ручной работы есть более удобные приемы.

Абсолютное расположение

Никто не настаивает на обязательном использовании в контейнере менеджера расположения, вы можете удалить его, вызвав метод setLayout(null). В этом случае вся ответственность за правильное расположение компонентов на экране ложится на ваши плечи: размеры и позиции компонентов придется задавать прямо в программе, вызывая для каждого компонента метод setBounds() с подходящим прямоугольником.

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

Единственным случаем, в котором приходится применять абсолютное расположение, являются графические приложения, где мы рисуем или располагаем компоненты поверх остального интерфейса, например, логотип компании. Часто для этого применяется компонент JXLayer или прозрачная панель (glass pane), которые мы обсуждали в главе 6.

Вложенные расположения

В предыдущем разделе, совмещая табличное и последовательное расположения, мы познакомились с примером так называемого вложенного расположения (nested layout), основная идея которого очень проста — вы создаете несколько контейнеров с различ-

Искусство расположения

177

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

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

Блочное расположение BoxLayout

Блочное расположение BoxLayout — прекрасная альтернатива всем остальным менеджерам расположения. Обладающее большими возможностями расположение BoxLayout

не сложнее BorderLayout.

Менеджер блочного расположения выкладывает компоненты в контейнер блоками: столбиком (по оси Y) или полоской (по оси X), при этом каждый отдельный компонент можно выравнивать по центру, по левому или по правому краям, а также по верху или по низу. Расстояние между компонентами по умолчанию нулевое, но для его задания существуют специальные классы (об этом чуть позже). Как располагаются компоненты, хорошо видно на рис. 7.1

BoxLayout.Y_AXIS (столбиком)

BoxLayout.X_AXIS (полоской)

Рис. 7.1. Блочное расположение

Стрелки показывают, в каком порядке происходит добавление компонентов в контейнер. Чтобы указать менеджеру, как нужно выкладывать компоненты — столбиком или полоской, используются константы из класса BoxLayout, также показанные на рисунке. Рассмотрим простой пример:

//Box1.java

//Блочное расположение import javax.swing.*; import java.awt.*;

public class Box1 extends JFrame {

178

ГЛАВА 7

public Box1() { super("Box1 — Y"); setSize(400, 200);

setDefaultCloseOperation( EXIT_ON_CLOSE );

//получаем панель содержимого

Container c = getContentPane();

//устанавливаем блочное расположение по

//оси Y (столбиком)

BoxLayout boxy = new BoxLayout(c, BoxLayout.Y_AXIS); c.setLayout(boxy);

//добавляем компоненты c.add( new JButton("Один")); c.add( new JButton("Два")); c.add( new JButton("Три"));

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

}

static class Box2 extends JFrame { public Box2() {

super("Box2 — X");

//устанавливаем размер и позицию окна setSize(400, 200);

setLocation(100, 100); setDefaultCloseOperation( EXIT_ON_CLOSE );

//получаем панель содержимого

Container c = getContentPane();

//устанавливаем блочное расположение по

//оси X (полоской)

BoxLayout boxx =

new BoxLayout(c, BoxLayout.X_AXIS); c.setLayout(boxx);

//добавляем компоненты c.add( new JButton("Один")); c.add( new JButton("Два")); c.add( new JButton("Три"));

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

}

}

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

Искусство расположения

179

new Runnable() {

public void run() { new Box1(); new Box2(); } });

}

}

В этом примере создаются два окна. В одном из них реализовано блочное расположение по оси Y, в другом — блочное расположение по оси X. Как легко убедиться, при блочном расположении компоненты действительно размещаются вплотную друг к другу.

Вы можете видеть, что конструктор класса BoxLayout несколько необычен — ему необходимо указать контейнер, в котором он будет функционировать. Ни в одном из рассмотренных нами прежде менеджеров расположения такого не требовалось. Впрочем, можно не строить блочное расположение вручную, а использовать вспомогательный класс Box из пакета javax.swing. В нем определены два статических метода:

public static Box createHorizontalBox() public static Box createVerticalBox()

Эти методы возвращают экземпляр класса Box, который создан специально для поддержки блочного расположения и унаследован от базового класса Swing JComponent. Поэтому объекты Box вы можете использовать как обычные контейнеры для компонентов, только с заранее установленным блочным расположением. Первый метод возвращает контейнер с горизонтальным блочным расположением, второй — с вертикальным.

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

180

ГЛАВА 7

эффекты, такие как полупрозрачные фоновые изображения. У компонента Box своего UI-представителя нет, и вы рискуете создать контейнер, выдающийся из общего внешнего вида приложения.

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

//com/porty/swing/BoxLayoutUtils.java

//Класс для удобной работы с блочным расположением package com.porty.swing;

import javax.swing.*;

public class BoxLayoutUtils {

//возвращает панель с установленным вертикальным

//блочным расположением

public static JPanel createVerticalPanel() { JPanel p = new JPanel();

p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); return p;

}

//возвращает панель с установленным горизонтальным

//блочным расположением

public static JPanel createHorizontalPanel() { JPanel p = new JPanel();

p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); return p;

}

}

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

Расстояние между компонентами

Нехорошо конечно, что при блочном расположении компоненты располагаются вплотную друг к другу. Получается неряшливо. Хотелось бы иметь возможность указывать расстояние между компонентами, причем между разными компонентами разное. Здесь нам на помощь вновь придет класс Box, неизменный помощник менеджера блочного расположения. В нем определены еще несколько статических методов, позволяющих создавать специальные невидимые компоненты определенного размера, которые помещаются между видимыми компонентами и разделяют их. Существует три типа таких компонентов — это распорки (struts), заполнители (glues) и фиксированные области (rigid areas).

Для начала рассмотрим распорки. Используют их как «палочки» (если хотите, «спички») определенного размера, которые ставят между компонентами и разделяют их. Так как при блочном расположении компоненты могут располагаться как по горизонтали, так и по вертикали, то и распорки бывают вертикальные и горизонтальные. Рассмотрим пример:

Искусство расположения

181

//BoxStruts.java

//Использование распорок при блочном расположении import javax.swing.*;

//используем наш новый класс

import com.porty.swing.BoxLayoutUtils;

public class BoxStruts extends JFrame { public BoxStruts() {

super("BoxStruts"); setSize(250, 200);

setDefaultCloseOperation( EXIT_ON_CLOSE );

//панель с вертикальным блочным расположением

JPanel p = BoxLayoutUtils.createVerticalPanel(); p.add(new JButton("Один"));

//создание вертикальной распорки p.add(Box.createVerticalStrut(15));

//новый компонент и распорка другого размера p.add(new JButton("Два")); p.add(Box.createVerticalStrut(5));

p.add(new JButton("Три"));

//панель с горизонтальным блочным расположением

JPanel p2 = BoxLayoutUtils.createHorizontalPanel();

//распорки можно ставить и перед компонентами p2.add(Box.createHorizontalStrut(10)); p2.add(new JButton("Один"));

//создание горизонтальной распорки p2.add(Box.createHorizontalStrut(25)); p2.add(new JButton("Два"));

//добавляем панели на север и юг окна

add(p, "North"); add(p2, "South");

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

}

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

new Runnable() {

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

}

}

182

ГЛАВА 7

В примере создаются две панели с вертикальным и горизонтальным блочным расположением. Для этого используется класс BoxLayoutUtils, описанный в предыдущем разделе. Между компонентами помещены распорки различных размеров, созданные классом Box — для создания вертикальных распорок предназначен метод createVerticalStrut(int), в качестве параметра которого указывается размер распорки в пикселах. Аналогично работает метод createHorizontalStrut(int), создающий горизонтальную распорку. Также никто не запрещает использовать распорки перед всей группой компонентов или после нее, чтобы отодвинуть их от границ окна.

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

Заполнители окончательно возводят блочное расположение в ранг универсального. Они работают как своего рода «пружины», помещаемые вертикально или горизонтально, соответственно текущему расположению, и раздвигают компоненты, разделяя все оставшееся в контейнере свободное место между собой. Именно с помощью заполнителей можно центрировать компоненты, размещать их не сверху, а снизу, равномерно распределять между ними все доступное пространство и т. д. Странно только, что создатели класса BoxLayout использовали название «glue» (заполнитель), а не оставили название «spring» (пружина), используемое в языке Smalltalk, откуда и пришла эта идея. Пояснит сказанное простой пример:

//BoxGlues.java

//Использование заполнителей import javax.swing.*;

import com.porty.swing.BoxLayoutUtils;

public class BoxGlues extends JFrame {

Искусство расположения

183

public BoxGlues() { super("BoxGlues"); setSize(250, 200);

setDefaultCloseOperation( EXIT_ON_CLOSE );

//панель с вертикальным блочным расположением

//в нее поместим все остальные панели

JPanel main = BoxLayoutUtils.createVerticalPanel(); // вертикальная панель

JPanel pVert = BoxLayoutUtils.createVerticalPanel();

//заполнитель перед компонентами отодвинет

//их вниз pVert.add(Box.createVerticalGlue()); pVert.add(new JButton("Один")); pVert.add(new JButton("Два"));

//горизонтальная панель

//теперь можно разместить компоненты по центру

JPanel pHor = BoxLayoutUtils.createHorizontalPanel(); pHor.add(Box.createHorizontalGlue());

pHor.add(new JButton("Три")); pHor.add(new JButton("Четыре")); pHor.add(Box.createHorizontalGlue());

//укладываем панели вертикально

main.add(pVert);

main.add(Box.createVerticalStrut(15));

main.add(pHor);

//добавляем панель в центр окна getContentPane().add(main);

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

}

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

}

}

Запустив программу с этим примером, вы увидите, как работают заполнители, и убедитесь, что пользоваться ими совсем просто. В вертикальную панель сначала помещается заполнитель, а затем пара кнопок. Заполнитель занимает все свободное место, и кнопки отодвигаются к самому низу панели. В горизонтальную панель вводятся два заполнителя по краям группы кнопок, они делят свободное место поровну (когда вы используете не один, а несколько заполнителей, они всегда делят свободное место поровну — то есть работают как пружины), и кнопки оказываются в центре панели. Создаются заполнители двумя статическими методами класса Box — вертикальный заполнитель создается методом Box.createVerticalGlue(), а горизонтальный — методом Box.createHorizontalGlue().

184

ГЛАВА 7

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

ВНИМАНИЕ

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

После запуска программы с примером у вас, возможно, возник еще один вопрос. Почему панели располагаются так необычно относительно друг друга? Вертикальный ряд кнопок находится не у левой границы окна, как в предыдущих примерах, а как-то не очень понятно, ближе к его середине. Дело в том, что для полного контроля за действиями менеджера расположения в классе Component предусмотрено еще два параметра, задающие выравнивание по осям X и Y, и менеджер BoxLayout их активно использует. Как направить эти параметры себе на пользу, мы узнаем в следующем разделе.

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

//BoxRigidAreas.java

//Пример использования фиксированных областей import javax.swing.*;

import com.porty.swing.BoxLayoutUtils; import java.awt.*;