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

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

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

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

205

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

Вход в систему

Имя:

Пароль:

ОК Отмена

Рис. 7.5. Схема диалогового окна для входа в систему

«Легко сказать — расчертите линиями!» — заявите вы: «Чем при этом следует руководствоваться в первую очередь?» Это верно, но рецептов в таких случаях, как правило, не существует. В случае использования блочного подхода можно принять во внимание следующее:

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

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

Необходимо помнить о выравнивании компонентов по осям.

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

Что же, с помощью рисунка 7.5 легко увидеть, какие менеджеры расположения нам могут помочь. Учитывая соображения о расширении текстовых полей в ширину, интерфейс построим, основываясь на горизонтальных «полосах». Для них выберем блочное расположение по оси X.

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

206

ГЛАВА 7

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

Итак, расстояние зададим распорками, а размеры надписей сделаем одинаковыми вручную. Как видно из рисунка 7.5, выравнивание должно быть центральным. А вот группа кнопок нами уже создавалась с помощью пары из табличного и последовательного расположения с выравниванием по правому краю. Эту идею используем и здесь. (Кстати, в качестве нехитрого упражнения можете реализовать эту же «полосу» с помощью одной только панели с блочным расположением.) Для получения окончательного результата все созданные горизонтальные панели уложим друг на друга с помощью менеджера вертикального блочного расположения. В таком случае эти панели необходимо согласованно выровнять по левую сторону оси.

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

//com/porty/swing/GUITools.java

//Набор инструментов для окончательной

//шлифовки и придания блеска интерфейсу package com.porty.swing;

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

public class GUITools {

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

//одинаковых размеров (минимальных,

//предпочтительных и максимальных).

public static void makeSameSize(JComponent... cs) {

//определение максимального размера

Dimension maxSize = cs[0].getPreferredSize(); for (JComponent c: cs) {

if ( c.getPreferredSize().width > maxSize.width ) { maxSize = c.getPreferredSize();

}

}

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

for (JComponent c : cs) { c.setPreferredSize(maxSize); c.setMinimumSize(maxSize); c.setMaximumSize(maxSize);

}

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

207

}

//позволяет исправить оплошность в

//размерах текстового поля JTextField

public static void fixTextFieldSize(JTextField field) { Dimension size = field.getPreferredSize();

//чтобы текстовое поле по-прежнему могло

//увеличивать свой размер в длину size.width = field.getMaximumSize().width;

//теперь текстовое поле не станет выше

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

field.setMaximumSize(size);

}

}

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

О назначении метода fixTextFieldSize() мы еще не говорили, но без него наше диалоговое окно может стать просто безобразным. Дело в том, что при появлении дополнительного пространства в окне текстовое поле JTextField увеличивается не только в длину, как ему и положено, но и в ширину, что совсем не вяжется с представлением об однострочном поле ввода. Это поведение не подходит для блочного расположения BoxLayout. Видимо, это связано с тем, что все текстовые компоненты в Swing унаследованы от базового класса JTextComponent, который и задает такие размеры, более подходящие для многострочных полей ввода и мини-редакторов. Чтобы исправить такую досадную оплошность, в классе GUITools и появился этот метод. Он оставляет длину текстового поля максимально возможной, а высоту заставляет оставаться на оптимальном уровне (который соответствует высоте текста).

Теперь, с классами BoxLayoutUtils и GUITools, все готово для того, чтобы в коде реализовать пользовательский интерфейс нашего диалогового окна:

//LoginDialog.java

//Этапы создание первоклассного

//пользовательского интерфейса на примере

//диалогового окна входа в систему import javax.swing.*;

import java.awt.*;

import com.porty.swing.BoxLayoutUtils; import com.porty.swing.GUITools;

208

ГЛАВА 7

public class LoginDialog extends JDialog { public LoginDialog(JFrame parent) {

super(parent, "Вход в систему");

//удаление окна при закрытии setDefaultCloseOperation(DISPOSE_ON_CLOSE);

//добавляем расположение в центр окна add(createGUI());

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

pack();

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

}

//этот метод будет возвращать панель с

//созданным расположением

private JPanel createGUI() {

//1. Создается панель, которая будет содержать

//все остальные элементы и панели расположения

JPanel main = BoxLayoutUtils.createVerticalPanel();

//Чтобы интерфейс отвечал требованиям Java,

//необходимо отделить его содержимое от

//границ окна на 12 пикселов.

//Для этого используем пустую рамку main.setBorder(

BorderFactory.createEmptyBorder(12,12,12,12));

//2. Поочередно создаются "полосы", на которые

//был разбит интерфейс на этапе анализа

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

JPanel name = BoxLayoutUtils.createHorizontalPanel();

JLabel nameLabel = new JLabel("Имя:"); name.add(nameLabel); name.add(BoxLayoutUtils.createHorizontalStrut(12)); JTextField nameField = new JTextField(15); name.add(nameField);

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

JPanel password = BoxLayoutUtils.createHorizontalPanel();

JLabel passwrdLabel = new JLabel("Пароль:"); password.add(passwrdLabel);

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

209

password.add(BoxLayoutUtils.createHorizontalStrut(12)); JTextField passwrdField = new JTextField(15); password.add(passwrdField);

// в) ряд кнопок

JPanel flow = new JPanel( new FlowLayout( FlowLayout.RIGHT, 0, 0) );

JPanel grid = new JPanel( new GridLayout( 1,2,5,0) );

JButton ok = new JButton("OK");

JButton cancel = new JButton("Отмена"); grid.add(ok);

grid.add(cancel); flow.add(grid);

//3. Проводятся необходимые действия по

//выравниванию компонентов, уточнению их

//размеров, приданию одинаковых размеров

//а) согласованное выравнивание

//вложенных панелей

BoxLayoutUtils.setGroupAlignmentX( Component.LEFT_ALIGNMENT, name, password, main, flow);

//б) центральное выравнивание надписей

//и текстовых полей

BoxLayoutUtils.setGroupAlignmentY( Component.CENTER_ALIGNMENT,

nameField, passwrdField, nameLabel, passwrdLabel);

//в) одинаковые размеры надписей к текстовым полям

GUITools.makeSameSize(nameLabel, passwrdLabel);

//д) устранение "бесконечной" высоты текстовых полей

GUITools.fixTextFieldSize(nameField); GUITools.fixTextFieldSize(passwrdField);

//4. Окончательный "сбор" полос в интерфейс main.add(name); main.add(BoxLayoutUtils.createVerticalStrut(12)); main.add(password); main.add(BoxLayoutUtils.createVerticalStrut(17)); main.add(flow);

//готово

return main;

}

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

210

ГЛАВА 7

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

public void run() {

new LoginDialog(new JFrame());

}

});

}

}

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

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

Далее проводятся действия по окончательному выравниванию компонентов (с помощью специально созданных нами классов BoxLayoutUtils и GUITools). Это важный момент, особенно при блочном расположении, и без него интерфейс выглядел бы просто нелепо. Размеры надписей к текстовым полям делаются одинаковыми.

В самом конце созданные «полосы» укладываются друг на друга с необходимыми интервалами. После этого остается только вывести диалоговое окно на экран и протестировать его поведение.

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

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

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

211

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

Реализация: гибкая сетка

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

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

//этот метод будет возвращать панель с

//созданным расположением

private JPanel createGUI() {

//1. Основная панель

//В конструкторе задаем "рост" второго столбца

JPanel main = new JPanel(new MigLayout("", "[][grow]"));

//первый ряд сетки main.add(new JLabel("Имя:"));

main.add(new JTextField(15), "gap rel, wrap rel, growx");

//второй ряд сетки

main.add(new JLabel("Пароль:"));

main.add(new JTextField(15), "gap rel, wrap unrel, growx");

//третий ряд сетки — кнопки одинакового размера

//пропускаем первую ячейку, разбиваем вторую на две

main.add(new JButton("OK"), "skip 1, split, sg buttons, align right");

main.add(new JButton("Отмена"), "sg buttons"); // готово

return main;

}

Для краткости мы опустили конструктор и метод main(), так как они абсолютно не отличаются от варианта с блочным расположением. Одно отличие — класс примера будет называться LoginDialog2. В том же самом методе createGUI() создается интерфейс приложе-

212

ГЛАВА 7

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

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

Не стоит думать, что вариант гибкой сетки всегда выигрывает у блочного подхода, как это получилось в случае с диалогом входа в систему. Если компонентов много, и они не всегда выровнены по сетке, попытки разместить их все в единой сетке могут быть слишком сложны и образовывать в результате код, который трудно прочитать и поддерживать. В таком случае к вашим услугам блочная техника расположения, или несколько более простых вложенных расположений. В принципе, это аналогия с расположением содержимого для HTML-страниц: у вас есть вариант пользоваться для расположения таблицами (<table>), или применять блоки (<div> или <span>).

Резюме

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

Глава 8. Вывод вспомогательной информации

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

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

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

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

Надписи JLabel

Компонент-надпись JLabel используется в Swing для вывода разнообразной информации, чаще всего состоящей из сочетания текста и значков, позволяя сообщить пользователю все, что вы захотите. Надо сказать, что надпись JLabel встречается в Swing буквально на каждом шагу: ее применяют для вывода вспомогательного текста в диалоговых окнах, для рисования значков и рисунков, и все сложные компоненты Swing, такие как деревья и таблицы, используют экземпляр надписи для прорисовки своих элементов: листьев деревьев и ячеек таблиц. Это настоящая «рабочая лошадка» библиотеки. Так что хорошее знание возможностей надписи JLabel в дальнейшем еще не раз позволит вам эффективно прорисовывать разнообразную информацию. Очень часто вместо создания собственного компонента со своей процедурой прорисовки проще использовать правильно настроенную надпись JLabel.

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

//Labels.java

//Настройка содержимого надписей import java.awt.*;

import javax.swing.*;

214

ГЛАВА 8

public class Labels extends JFrame implements SwingConstants { public Labels() {

super("Labels");

//при закрытии окна заканчиваем работу setDefaultCloseOperation(EXIT_ON_CLOSE);

//самая простая надпись

JPanel contents = new JPanel(); JLabel l1 = new JLabel("Ваше имя:"); JTextField name = new JTextField(20); contents.add(l1); contents.add(name);

// надпись со значком

JLabel l2 = new JLabel(new ImageIcon("monkey.gif")); adjustLabel(l2);

l2.setHorizontalAlignment(LEFT);

contents.add(l2);

//надпись с нестандартным выравниванием

JLabel l3 = new JLabel("Текст и значок", new ImageIcon("bulb.gif"), RIGHT);

adjustLabel(l3);

l3.setVerticalTextPosition(BOTTOM);

l3.setHorizontalTextPosition(LEFT);

contents.add(l3);

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

}

// метод производит специальную настройку надписи private void adjustLabel(JLabel l) {

l.setOpaque(true);

l.setBackground(Color.white); l.setPreferredSize(new Dimension(250, 100));

}

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

new Runnable() {

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

}

}