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

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

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

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

165

}

// предпочтительный размер для контейнера

public Dimension preferredLayoutSize(Container c) { return calculateBestSize(c);

}

private Dimension size = new Dimension(); // вычисляет оптимальный размер контейнера

private Dimension calculateBestSize(Container c) { // сначала вычислим длину контейнера

Component[] comps = c.getComponents(); int maxWidth = 0;

for (Component comp : comps) { int width = comp.getWidth();

// поиск компонента с максимальной длиной if (width > maxWidth) maxWidth = width;

}

//длина контейнера с учетом левого отступа size.width = maxWidth + GAP;

//вычисляем высоту контейнера

int height = 0;

for (Component comp : comps) { height += GAP;

height += comp.getHeight();

}

size.height = height; return size;

}

// проверим работу нового менеджера public static void main(String[] args) {

SwingUtilities.invokeLater( new Runnable() {

public void run() {

JFrame frame = new JFrame("VerticalLayout"); frame.setDefaultCloseOperation(

JFrame.EXIT_ON_CLOSE);

//панель будет использовать новое расположение

JPanel contents = new JPanel( new VerticalLayout());

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

166

ГЛАВА 7

contents.add(new JButton("Один")); contents.add(new JButton("Два")); contents.add(new JTextField(30)); frame.add(contents); frame.setVisible(true); frame.pack(); } });

}

}

Наш первый менеджер расположения, получивший название VerticalLayout, как и положено любому менеджеру расположения, реализует интерфейс LayoutManager. Первый метод, который нам придется реализовать, одновременно является и самым важным — именно в методе layoutContainer() менеджер расположения должен расположить все компоненты, содержащиеся в контейнере, по своим местам. Поведение нашего менеджера расположения незамысловато: он размещает компоненты в вертикальный ряд, отделяя их расстоянием в 5 пикселов (расстояние хранится в переменной GAP), все компоненты имеют предпочтительный размер. Чтобы реализовать такое поведение, понадобились следующие действия: мы просмотрели массив содержащихся в контейнере компонентов (получить этот массив позволяет метод контейнера getComponents()), получили для каждого из компонентов предпочтительный размер и указали (методом setBounds()) позицию компонента на экране, постоянно отслеживая текущую координату по оси Y, увеличивая ее на высоту очередного компонента с учетом «декоративного» расстояния из переменной GAP. Так все компоненты будут располагаться друг над другом, отделенные отступами3. Обратите внимание, что мы отделяем компоненты и от левой границы контейнера на то же самое расстояние.

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

Следующими являются два метода, сообщающие предпочтительный (preferredLayoutSize()) и минимальный (minimumLayoutSize()) размеры контейнера при использовании

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

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

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

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

167

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

После реализации всех методов интерфейса LayoutManager мы сможем проверить новый менеджер расположения в работе. Для этого в методе main() мы создаем небольшое окно с рамкой JFrame и добавляем в это окно панель JPanel , устанавливая для нее наш новый менеджер расположения. В панель добавляется пара кнопок и довольно большое текстовое поле, после чего окно выводится на экран. Чтобы оценить правильность расчета нашим менеджером оптимальных размером, мы вызываем метод pack(), который придает окну предпочтительный размер. Запустив программу с примером, вы сможете оценить, как компоненты располагаются вертикально друг над другом, и верно ли был проведен расчет оптимальных размеров.

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

Стандартные менеджеры расположения

В пакете разработки JDK 1.7 имеется довольно много готовых стандартных менеджеров расположения. Условно их можно разделить на несколько групп: очень простые (к ним относятся FlowLayout, GridLayout и BorderLayout), универсальные (к таким без всяких сомнений стоит отнести расположения BoxLayout, GroupLayout и GridBagLayout) и специализированные (CardLayout и SpringLayout). Универсальные менеджеры мы рассмотрим в последнюю очередь и наиболее подробно, а пока быстро «пройдемся» по всем остальным менеджерам.

Полярное расположение BorderLayout

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

Надо сказать, что работает этот менеджер немного не так, как все остальные — чтобы добавить с его помощью компонент, в методе add() необходимо указать дополнительный

168

ГЛАВА 7

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

Значение BorderLayout.NORTH или строка «North» — компонент располагается вдоль верхней (северной) границы окна и растягивается на всю его ширину. Обычно так размещается панель инструментов.

Значение BorderLayout.SOUTH или строка «South» — компонент располагается вдоль нижней (южной) границы и растягивается на всю ширину окна. Такое положение идеально для строки состояния.

Значение BorderLayout.WEST или строка «West» — компонент располагается вдоль левой (западной) границе окна и растягивается на всю его высоту, однако при этом учитываются размеры северных и южных компонентов (они имеют приоритет).

Значение BorderLayout.EAST или строка «East» — компонент располагается вдоль правой (восточной) границы окна. В остальном его расположение аналогично западному компоненту.

Значение BorderLayout.CENTER или строка «Center» — компонент помещается в центр окна, занимая максимально возможное пространство.

СОВЕТ

На север помещайте панель инструментов вашего приложения. На юг помещайте строку состояния. Оставляйте западные и восточные зоны окна свободными — только в этом случае панель инструментов можно будет перетаскивать. Для главного окна вашего приложения всегда используйте располо-

жение BorderLayout.

Рассмотрим простой пример. В нем создается окно JFrame, в котором менеджер BorderLayout используется по умолчанию. Во все доступные зоны добавляются компоненты:

//BorderLayoutSample.java

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

public class BorderLayoutSample extends JFrame { public BorderLayoutSample() {

super("BorderLayoutSample"); setSize(400, 300);

setDefaultCloseOperation( EXIT_ON_CLOSE );

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

//в качестве параметров можно использовать строки add(new JButton("Север"), "North");

add(new JButton("Юг"), "South");

//… или константы из класса BorderLayout

add(new JLabel("Запад"), BorderLayout.WEST); add(new JLabel("Восток"), BorderLayout.EAST);

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

169

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

//автоматически добавится в центр

add(new JButton("Центр")); // выводим окно на экран setVisible(true);

}

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

new Runnable() {

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

}

}

В примере есть несколько интересных мест. Во-первых, обратите внимание на то, что метод установки менеджера расположения setLayout() не вызывался, потому что в окнах JFrame (а также в окнах без рамки JWindow и диалоговых окнах JDialog) расположение BorderLayout применяется по умолчанию. Во-вторых, будьте осторожнее с использованием строк в качестве параметров метода add(). Так легко сделать трудно обнаруживаемую ошибку, в то время как ошибку при использовании констант сразу же обнаружит компилятор. Картина на экране в итоге получается следующая:

Как легко заметить, возможности полярного расположения довольно ограничены. Оно создано специально для окон, но, тем не менее, иногда может помочь и в более сложных случаях. Об этом мы поговорим немного позднее.

Последовательное расположение FlowLayout

Последовательное расположение FlowLayout работает очень просто, но тем не менее эффективно. Оно выкладывает компоненты в контейнер, как пирожки на противень: слева направо, сверху вниз, начиная с верхнего левого угла контейнера. Это расположение устанавливается по умолчанию в панелях JPanel. Основным свойством его следует признать то, что компонентам всегда придается предпочтительный размер (например, ярлык с текстом JLabel соответствует по размерам тексту). Рассмотрим простой пример:

//FlowLayoutSample.java

//Последовательное расположение

170

ГЛАВА 7

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

public class FlowLayoutSample extends JFrame { public FlowLayoutSample() {

super("FlowLayoutSample"); setSize(400, 200);

setDefaultCloseOperation( EXIT_ON_CLOSE );

//устанавливаем последовательное расположение с

//выравниванием компонентов по центру setLayout( new FlowLayout( FlowLayout.CENTER ));

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

add( new JButton("Один")); add( new JButton("Два")); add( new JButton("Три")); // выводим окно на экран setVisible(true);

}

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

new Runnable() {

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

}

}

Конструктор класса FlowLayout имеет три перегруженные версии: без параметров, с параметрами выравнивания компонентов, с параметрами выравнивания компонентов и расстояний между ними. В примере использована вторая версия, устанавливающая последовательное расположение по центру.

Запустите программу с примером, и вы увидите, как работает менеджер последовательного расположения.

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

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

171

окна по вертикали — вы увидите, что компоненты, не помещающиеся в окно, просто исчезают из поля зрения! Это не какой-то недостаток последовательного расположения, как может показаться на первый взгляд, а только следствие его главного свойства: при последовательном расположении всегда сохраняется предпочтительный размер компонентов. Менеджер FlowLayout неизменно работает по такому правилу, и если места в контейнере становится мало, то он просто прячет «лишние» компоненты, а не уменьшает их размеры. Вторым по важности свойством менеджера расположения FlowLayout следует признать то, что при вызове метода preferredLayoutSize() или minimumLayoutSize(), позволяющего узнать предпочтительный и минимальный размеры контейнера, в котором этот менеджер действует, метод возвращает размер, соответствующей ситуации расположения всех компонентов в одну строку. Это особенно важно при совмещении последовательного расположения с другими вариантами расположения. Вследствие этого свойства менеджеры других вариантов расположения будут стараться найти достаточно места для размещения компонентов в одну строку. Вскоре мы увидим совмещение в действии.

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

Табличное расположение GridLayout

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

//GridLayoutSample.java

//Табличное расположение import java.awt.*;

import javax.swing.*;

public class GridLayoutSample extends JFrame { public GridLayoutSample() {

super("GridLayoutSample"); setSize(300, 200); setLocation(100, 100);

setDefaultCloseOperation( EXIT_ON_CLOSE );

//вспомогательная панель

JPanel grid = new JPanel();

//первые два параметра конструктора GridLayout -

//количество строк и столбцов в таблице

//вторые два — расстояние между ячейками по X и Y GridLayout gl = new GridLayout(2, 0, 5, 12); grid.setLayout(gl);

172 ГЛАВА 7

// создаем 8 кнопок

for (int i=0; i<8; i++) {

grid.add(new JButton("Кнопка " + i));

}

//помещаем нашу панель в центр окна add(grid);

//устанавливаем оптимальный размер pack();

//показываем окно

setVisible(true);

}

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

new Runnable() {

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

}

}

Обратите внимание на то, как в примере задается произвольное количество столбцов — для этого в конструкторе вместо конкретного числа столбцов указывается ноль. Аналогично можно задать и произвольное количество строк в таблице. Главное, чтобы оба числа не были нулями. В нашем примере GridLayout разделит компоненты так, чтобы они помещались в два ряда, неважно, как много столбцов для этого придется создать.

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

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

Все компоненты всегда выводятся на экран, как бы ни было велико или малодоступно пространство.

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

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

173

GridLayout в дополнительной панели с последовательным расположением FlowLayout, которое наоборот, никогда не делает содержащиеся в нем компоненты больше их предпочтительного размера, или использовать другое расположение с такими же свойствами (GridBagLayout). Чтобы проиллюстрировать сказанное, создадим элемент пользовательского интерфейса, встречающийся практически в любом диалоговом окне, а именно строку кнопок (обычно это кнопки OK и Отмена). Табличное расположение придаст кнопкам одинаковый размер, а последовательное расположение не даст им «расплыться» и заодно выровняет их по правому краю:

//CommandButtons.java

//Создание панели командных кнопок import javax.swing.*;

import java.awt.*;

public class CommandButtons extends JFrame { public CommandButtons() {

super("CommandButtons"); setSize(350, 250); setLocation(150, 100);

setDefaultCloseOperation( EXIT_ON_CLOSE );

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

//выравнивания размеров кнопок

JPanel grid = new JPanel(

new GridLayout(1, 2, 5, 0) );

//добавляем компоненты grid.add( new JButton("OK")); grid.add( new JButton("Отмена"));

//помещаем полученное в панель с последовательным

//расположением, выравненным по правому краю

JPanel flow = new JPanel(

new FlowLayout( FlowLayout.RIGHT )); flow.add(grid);

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

add(flow, BorderLayout.SOUTH ); // выводим окно на экран setVisible(true);

}

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

new Runnable() {

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

}

}

174

ГЛАВА 7

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

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

Динамические вкладки и CardLayout

Хотя менеджер расположения CardLayout поддерживается в пакетах JDK с самых первых версий, при создании пользовательского интерфейса им практически не пользуются. Менеджер CardLayout до появления библиотеки Swing использовался для создания так называемых вкладок (tabs), выбирая которые, можно было поочередно открывать доступ к панелям, занимающие одно и то же место. В библиотеке Swing имеется специальный класс JTabbedPane, который берет все заботы по организации вкладок на себя, а вам остается лишь добавлять их. Мы его вскоре изучим.

Вариантом применения CardLayout остается динамический интерфейс, которому не нужны визуальные вкладки, которые предоставляет JTabbedPane, но нужна возможность динамически, во время работы программы, менять одни панели на другие. Но такая возможность легко реализуется и без CardLayout — необходимо убрать из контейнера старую панель методом remove(), добавить новую панель, и провести проверку корректности контейнера методом revalidate(). Таким образом, область применения менеджера CardLayout весьма ограничена и мы даже не станем рассматривать его на примере.

Менеджер расположения SpringLayout

В пакете разработки JDK имеется универсальный менеджер расположения, способный помочь добиться любого, даже самого сложного расположения компонентов. Это менеджер расположения SpringLayout из пакета javax.swing. Однако несмотря на универсальность, действие его весьма специфично и не похоже на действие ни одного из уже знакомых нам менеджеров расположения. С каждым компонентом ассоциируется особый информационный объект Spring, который позволяет задать расстояние (в пикселах) между парой границ различных компонентов. Границ у компонента четыре — это его северная, восточная, западная и южная сторона. Можно задавать расстояние и между границами одного и того же компонента: например, задав расстояние между северной и южной сторонами одного компонента, вы укажете его высоту. По умолча-