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

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

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

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

185

public class BoxRigidAreas extends JFrame { public BoxRigidAreas() {

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

setDefaultCloseOperation( EXIT_ON_CLOSE ); // вертикальная панель

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

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

JPanel pHor = BoxLayoutUtils.createHorizontalPanel(); pHor.add(new JButton("Два"));

//размер пространства задается в виде объекта

//Dimension из пакета java.awt pHor.add(Box.createRigidArea(new Dimension(50,120))); pHor.add(new JButton("Три"));

pVert.add(pHor);

//добавляем вертикальную панель в центр окна add(pVert);

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

setVisible(true);

}

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

new Runnable() {

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

}

}

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

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

186

ГЛАВА 7

запускать программу с примером — вертикальная панель снова уплывает куда-то к середине окна. Вскоре мы возьмем эту ситуацию под контроль, и любое расположение будет у нас в руках.

Проблема с распорками

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

Поэтому лучше создать распорки самим, не применяя стандартные. Тут нам помогут фиксированные области, у которых одно расстояние будет распоркой, а второе всегда нулевым. Именно такое поведение нам и требуется при разделении компонентов. Добавим в наш вспомогательный класс BoxLayoutUtils еще пару удобных методов:

//com/porty/swing/BoxLayoutUtils.java

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

import javax.swing.*;

public class BoxLayoutUtils

{

//создает горизонтальную распорку

//фиксированного размера

public static Component createHorizontalStrut(int size) { return Box.createRigidArea(new Dimension(size, 0));

}

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

//фиксированного размера

public static Component createVerticalStrut(int size) { return Box.createRigidArea(new Dimension(0, size));

}

// далее идут методы, описанные ранее

}

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

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

187

Выравнивание компонентов по осям

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

Каждый компонент (унаследованный от класса java.awt.Component) имеет два параметра, служащие для его выравнивания по осям X и Y, то есть для его более точного размещения в контейнере. Что это за оси? Рассмотрим сначала вертикальную панель, где компоненты располагаются сверху вниз. Мы можем захотеть изменить горизонтальную позицию компонентов (то есть позицию по оси X) такой панели (вертикальные позиции жестко заданы положением компонентов, распорок и заполнителей) — поместить их слева, справа или по центру. Так вот, сделать это путем выравнивания по осям нельзя.

ВНИМАНИЕ

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

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

Component.CENTER_ALIGNMENT — вертикальная ось будет проходить через середину компонента;

Component.LEFT_ALIGNMENT — компонент будет прижат к вертикальной оси своей левой границей;

Component.RIGHT_ALIGNMENT — компонент будет прижат к вертикальной оси своей правой границей.

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

LEFT

LEFT

RIGHT

CENTER

LEFT

RIGHT

RIGHT

LEFT

RIGHT

Рис. 7.2. Блочное расположение с горизонтальным выравниванием

188

ГЛАВА 7

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

//BoxAlignment.java

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

//компонентов по осям

import javax.swing.*;

import com.porty.swing.BoxLayoutUtils;

public class BoxAlignment extends JFrame { public BoxAlignment() {

super("BoxAlignment"); setSize(400, 150);

setDefaultCloseOperation( EXIT_ON_CLOSE ); // вертикальная панель

JPanel pv = BoxLayoutUtils.createVerticalPanel();

//кнопка с выравниванием по левой границе

JButton jb = new JButton("Левая граница"); jb.setAlignmentX(LEFT_ALIGNMENT); pv.add(jb);

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

jb = new JButton("Выравнивание по центру"); jb.setAlignmentX(CENTER_ALIGNMENT); pv.add(jb);

//наконец, кнопка с выравниванием по правому краю jb = new JButton("Правая граница"); jb.setAlignmentX(RIGHT_ALIGNMENT);

pv.add(jb);

//добавляем панель в центр окна

add(pv);

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

}

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

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

189

new Runnable() {

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

}

}

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

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

Component.CENTER_ALIGNMENT — горизонтальная ось будет проходить через середину компонента;

Component.TOP_ALIGNMENT — компонент будет прижат к горизонтальной оси своей верхней границей;

Component.BOTTOM_ALIGNMENT — компонент будет прижат к горизонтальной оси своей нижней границей.

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

TOP

TOP

TOP

BOTTOM

 

 

CENTER

 

 

TOP

 

 

Рис. 7.3. Блочное расположение с вертикальным выравниванием

Если все компоненты имеют выравнивание TOP_ALIGNMENT, то ось проходит сверху контейнера, а все компоненты прижимаются к ней верхней границей, как показано справа на рис. 7.3. Схема для выравнивания BOTTOM_ALIGNMENT выглядит аналогично,

190

ГЛАВА 7

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

Кстати, если вы просмотрите документацию по классу Component, то увидите, что для задания параметров выравнивания компонентов используются числа с плавающей запятой (float) от 0 до 1. Можно сказать, что они определяют, какая часть компонента находится за осью. Так, например, константы RIGHT_ALIGNMENT и TOP_ALIGNMENT равны нулю. Это значит, что весь компонент находится по одну сторону от оси (с правой или с нижней стороны, соответственно). Если увеличивать значения выравнивания, то компонент будет потихоньку «переползать» за ось и, в конце концов, окажется по другую ее сторону. Константы LEFT_ALIGNMENT и BOTTOM_ALIGNMENT как раз равны единице (то есть компонент находится целиком за осью). Так что это гораздо более тонкий инструмент, чем кажется поначалу, — значения констант соответствуют наиболее вероятным вариантам выравнивания компонентов, но вы также можете и напрямую указывать свои значения, что позволяет очень точно выстраивать компоненты относительно друг друга.

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

С другой стороны, нет ничего проще совмещения нескольких панелей с блочным расположением, содержащих по несколько компонентов с одинаковым выравниванием. Любой пользовательский интерфейс можно разбить на горизонтальные и вертикальные полосы с компонентами, применяя для них подобные панели. Именно такой способ создания интерфейса дает наибольшие преимущества разработчику, и мы всячески рекомендуем его вам. А чтобы задание параметров выравнивания компонентов не отнимало у вас слишком много времени и сил, добавим в наш вспомогательный класс BoxLayoutUtils еще пару методов:

//com/porty/swing/BoxLayoutUtils.java

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

import javax.swing.*;

public class BoxLayoutUtils

{

//задает единое выравнивание по оси X для

//группы компонентов

public static void setGroupAlignmentX(float alignment, JComponent... cs) {

for (JComponent c : cs) { c.setAlignmentX(alignment);

}

}

//задает единое выравнивание по оси Y для

//группы компонентов

public static void setGroupAlignmentY(float alignment,

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

191

JComponent... cs) { for (JComponent c : cs) {

c.setAlignmentY(alignment);

}

}

// далее идут методы, описанные ранее

}

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

Если вы помните, в предыдущем разделе был показан пример, который почему-то работал не так, как нам хотелось. Теперь понятно, почему — у панели было центральное выравнивание, а у кнопки — выравнивание по левой границе. Используя новые методы класса BoxLayoutUtils, можно легко согласовать параметры выравнивания всех компонентов, и они займут положенные им места, задуманные нами с самого начала.

Расположение по группам: GroupLayout

Расположение GroupLayout было специально создано в поддержку визуального редактора для расположения компонентов Swing в редакторе NetBeans (редактор назывался Matisse), а затем перекочевало в JDK. Суть его состоит в том, что компоненты располагаются в вертикальных и горизонтальных группах. Похоже на совмещение нескольких блочных расположений в одном, однако особенностью является то, что один и тот же компонент вполне может участвовать в нескольких группах одновременно. К примеру, текстовые поля могут участвовать в горизонтальных группах вместе с другими компонентами, например надписями и кнопками, и одновременно с этим в вертикальных группах, что позволяет сделать их размер одинаковым. В группе можно указывать расстояние между компонентами. Окончательный вариант расположения создается двумя главными группами, вертикальной и горизонтальной.

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

Гибкая сетка: GridBagLayout

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

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

192

ГЛАВА 7

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

Управление расположением компонентов в GridBagLayout осуществляется отдельным объектом под названием GridBagConstraints, который полностью описывает расположение компонента в сетке и его особенности. Настроив его, вы передаете его в метод add() вместе с добавляемым в контейнер компонентом. К сожалению, именно этот объект делает GridBagLayout такой удобной целью для нападок, а код с его применением напоминает прекрасно перемешанное спагетти. Дело в том, что все настройки хранятся в нем в виде полей (что противоречит концепциям объектно-ориентированного программирования), имена этих полей иногда не соответствуют производимому им эффекту, а количество «волшебных цифр» и избыточной информации поражает воображение и лишает код читаемости.

Давайте начнем с положения компонента в таблице. Его позиция определяется номером ячейки по вертикали и горизонтали:

gridx

(0,0) (1,0)

gridy

(0,1) (1,1)

(0,2)

Координаты в таблице, таким образом, хранятся в полях gridx и gridy. Так как указывать каждый раз новые координаты несколько утомительно, GridBagConstraints любезно предоставляет нам константу RELATIVE, согласно которой компонент будет добавлен в тот же ряд и следующий столбец за последним добавленным компонентов. Такое поведение напомнит нам последовательное расположение FlowLayout, тем более что по умолчанию компоненту придается предпочтительный размер. По умолчанию, если вы не указываете значение полей gridx и gridy, как раз RELATIVE и применяется, располагая компоненты в один ряд.

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

gridwidth — количество ячеек, занимаемое компонентов по горизонтали, «длина» компонента в ячейках

gridheight — количество ячеек, занимаемое компонентов по вертикали, «высота» компонента в ячейках

Очень часто компонент должен занять весь ряд или столбец, независимо от того, сколько там ячеек. Для этих случаев предусмотрено значение GridBagConstraints. REMINDER, которое и говорит, что компонент займет все оставшиеся ячейки по одной из осей или даже по обеим. Это тем более важно, что компонент в этом случае не зависит от появления большего количества ячеек в каком-то из рядом или столбцов, но все равно займет все ячейки.

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

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

 

193

 

insets.top

 

 

anchor = NORTH

 

insets.

fill

insets.

left

right

 

Компонент

 

anchor =

anchor =

 

WEST

EAST

 

 

anchor = SOUTH

 

 

insets.bottom

 

Итак, согласно иллюстрации мы получаем следующее:

 

fill — определяет, будет ли компонент заполнять все пространство ячейки, и если будет, то как. Можно применить константы HORIZONTAL, VERTICAL и BOTH, что будет соответственно обозначать заполнение всего размера ячейки по горизонтали, вертикали, или всего пространства ячейки (по обоим направлениям). По умолчанию значение этого поля в объекте GridBagConstraints равняется NONE, что означает, что компонент остается предпочтительного размера и не желает изменяться вместе с размерами ячейки.

anchor — поле, значение которого указывает, к какому краю ячейки компоненту следует «прилепиться». На рисунке показаны наиболее вероятные варианты, которые, как мы видим, названы по сторонам света, к примеру, выравнивание по левому краю называется WEST (выравнивание на запад). Есть также возможность «прилеплять» компоненты к углам ячейки.

insets — важные для любого прилично выглядящего интерфейса отступы между компонентами. Отступы задаются в виде объекта Insets, и определяют пространство между ячейками сверху, снизу, слева и справа, в пикселях.

Итак, основная идея GridBagLayout кажется понятной, и ячейки различных размеров, с полным контролем над их содержимым должны позволять создавать любые интерфейсы. Рассмотрим простой пример:

//GridBagStart.java

//Первые опыты с расположением GridBagLayout import java.awt.*;

import javax.swing.*;

public class GridBagStart extends JFrame { public GridBagStart() {

super("GridBagStart");

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

194

ГЛАВА 7

//устанавливаем расположение компонентов setLayout(new GridBagLayout());

//добавляем две кнопки, ячейки по умолчанию add(new JButton("Привет"));

add(new JButton("Отмена"));

//настройка ячейки для текстового поля

GridBagConstraints textFieldConstraints = new GridBagConstraints();

//заполнение ячейки по горизонтали textFieldConstraints.fill = GridBagConstraints.HORIZONTAL;

//просим занять все оставшиеся ячейки textFieldConstraints.gridwidth =

GridBagConstraints.REMAINDER; textFieldConstraints.weightx = 1.0f;

add(new JTextField(10), textFieldConstraints);

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

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

}

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

new Runnable() {

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

}

}

Здесь мы устанавливаем в качестве менеджера расположения GridBagLayout, добавляем две кнопки, причем, не указывая им явно объекта GridBagConstraints. Как мы помним, это означает, что они будут добавляться в ряд, друг за другом, и иметь предпочтительный размер (именно так по умолчанию настроены поля gridx, gridy и fill). Далее мы добавим текстовое поле, и хотим, чтобы оно заняло все оставшееся место контейнера. Для этого мы указываем в объекте GridBagConstraints что текстовое поле занимает все оставшиеся в ряду ячейки и заполняет ячейку по горизонтали. На первый взгляд, все просто и должно дать нужный результат. Запустив пример, мы увидим следующее: