Искусство расположения |
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
//Последовательное расположение