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(); } });
}
}