
Портянкин И. Swing
.pdf
Меню и панели инструментов |
265 |
}
public void actionPerformed(ActionEvent e) { System.exit(0);
}
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new MenuSystem(); } });
}
}
Итак, в этом примере создается относительно простая система меню. Как мы уже отмечали, элементы меню — это фактически кнопки, собранные в список. Списки элементов меню, или правильнее выпадающие меню (drop-down menus), реализованы в Swing классом JMenu. Именно они создаются методами createFileMenu() и createWhoMenu(), внутри которых в выпадающие меню методом add() добавляются разнообразные элементы меню, в том числе элементы-флажки и элементыпереключатели. После этого остается вывести созданные выпадающие меню на экран, для чего служит так называемая строка меню (menu bar), создать которую позволяет класс JMenuBar. В нем также есть метод add(), только не для элементов меню, а для выпадающих меню JMenu. Разместив все выпадающие меню своего приложения в строке меню, вы можете поместить ее в окно, вызвав предназначенный для этого метод setJMenuBar().
Что касается элементов меню, то это те же кнопки, флажки и переключатели. Вы можете указывать для них названия, значки и мнемоники, создавать их на основе интерфейса Action, и все это проиллюстрирует наш пример. Единственное новшество в примере — это разделитель (separator), который позволяет организовать смысловые группы в выпадающих меню. В примере показано, как его создать, а подробнее мы поговорим о нем чуть позже.
Что же, система меню создается действительно просто, и ничего экстраординарного в этом процессе нет. Однако меню Swing способны и на многое другое, поэтому давайте рассмотрим их подробнее.
266 |
ГЛАВА 10 |
Строка меню JMenuBar
Главное, что нужно помнить при создании строки меню JMenuBar в своей программе, — это самый обыкновенный контейнер, ничем не отличающийся от панели JPanel и обладающий теми же самыми свойствами. Не нужно думать, что строка меню рождена только для работы с выпадающими меню. Вы можете смело добавлять в нее всевозможные компоненты, например надписи со значками или раскрывающиеся списки. Беззастенчиво заглянув внутрь класса JMenuBar, можно увидеть, что даже менеджер расположения у строки меню не какой-то экзотический, а прекрасно знакомый нам менеджер BoxLayout с расположением компонентов по горизонтали. Так что создание с виду совершенно новаторского меню оказывается весьма простым делом. Например, можно сделать со строкой меню следующее:
//TrickyMenuBar.java
//Полоска меню JMenuBar может многое import javax.swing.*;
import java.awt.*;
public class TrickyMenuBar extends JFrame { public TrickyMenuBar() {
super("TrickyMenuBar"); setDefaultCloseOperation( EXIT_ON_CLOSE );
//создаем главную полоску меню
JMenuBar menuBar = new JMenuBar();
//добавляем в нее выпадающие меню menuBar.add(new JMenu("Файл")); menuBar.add(new JMenu("Правка"));
//мы знаем, что используется блочное
//расположение, так что заполнитель
//вполне уместен menuBar.add(Box.createHorizontalGlue());
//теперь поместим в полоску меню
//не выпадающее меню, а надпись со значком
JLabel icon = new JLabel(
new ImageIcon("images/download.gif")); icon.setBorder(
BorderFactory.createLoweredBevelBorder());
menuBar.add(icon);
//помещаем меню в наше окно setJMenuBar(menuBar);
//выводим окно на экран
setSize(300, 200); setVisible(true);
}
public static void main(String[] args) {

Меню и панели инструментов |
267 |
SwingUtilities.invokeLater( new Runnable() {
public void run() { new TrickyMenuBar(); } });
}
}
В примере мы сначала добавили в строку меню традиционные выпадающие меню (они, правда, для экономии места были оставлены пустыми), а затем, предварительно использовав заполнитель, поместили в строку меню самую обычную надпись со значком, установив для нее тисненую рамку. Мы специально использовали анимированный GIF-файл, чтобы пример произвел еще более сильный эффект. Не правда ли, напоминает современные браузеры? И все, что мы сделали для этого, — это добавили два компонента.
Отметьте также, что размещение строки меню в окне осуществляется не с помощью обычного метода add() и менеджера расположения, а с помощью специального метода setJMenuBar(). Этот метод на самом деле принадлежит не классу окна JFrame, а корневой панели JRootPane, которая и заботится о том, чтобы строка меню занимала подобающее ей положение на вершине окна, оставляя вам все пространство в панели содержимого.
Таким образом, знание того факта, что строка меню — это обычный контейнер, позволяет творить со строкой меню просто невероятные вещи. Это хороший пример того, как важна при разработке библиотеки ее прозрачность — никто не скрывает от вас тонкости реализации, и то же можно сказать обо всех компонентах. Знание основ устройства библиотеки — гораздо более мощное оружие, чем может показаться на первый взгляд.
Впрочем, не стоит сильно увлекаться созданием абсолютно нового облика меню в вашей программе, иначе пользователям трудно будет быстро освоиться с ней. Все-таки вид и поведение меню уже устоялись, и в привычном облике работать с меню проще.
Выпадающие меню JMenu и разделители JSeparator
Как мы уже отмечали, для упорядочения элементов меню служат выпадающие меню JMenu. Интересно, что класс JMenu унаследован от обычного элемента меню JMenuItem, и его функция заключается только в том, чтобы при щелчке мышью показать в нужном месте экрана всплывающее меню, в котором и содержатся все добавленные элементы меню. Ничего хитрого в этом классе нет, и сказать о нем можно только то, что он позволяет организовывать вложение меню любой сложности. Для этого вы просто вкладываете выпадающие меню друг в друга, и получаете перечни элементов меню произвольной длины (каскадные меню).
Для того чтобы организовать в меню несколько групп элементов, используются разделители JSeparator. Мы можете добавлять их в выпадающие меню, создавая напрямую, или же использовать для этого специальный метод addSeparator(). Кроме того разделите-
268 |
ГЛАВА 10 |
ли можно применять и вне меню в качестве разделительных линий, в том числе вертикальных. Рассмотрим пример:
//CascadedMenus.java
//Создание вложенных меню любой сложности import javax.swing.*;
import java.awt.*;
public class CascadedMenus extends JFrame { public CascadedMenus() {
super("CascadedMenus"); setDefaultCloseOperation( EXIT_ON_CLOSE );
//создаем строку главного меню
JMenuBar menuBar = new JMenuBar();
//создаем выпадающее меню
JMenu text = new JMenu("Текст");
//и несколько вложенных меню
JMenu style = new JMenu("Стиль"); JMenuItem bold = new JMenuItem("Жирный");
JMenuItem italic = new JMenuItem("Курсив"); JMenu font = new JMenu("Шрифт");
JMenuItem arial = new JMenuItem("Arial"); JMenuItem times = new JMenuItem("Times"); font.add(arial);
font.add(times);
//размещаем все в нужном порядке style.add(bold); style.add(italic); style.addSeparator(); style.add(font);
text.add(style);
menuBar.add(text);
//помещаем меню в окно setJMenuBar(menuBar);
//разделитель может быть полезен не только в меню
((JComponent)getContentPane()).setBorder( BorderFactory.createEmptyBorder(0, 5, 0, 0));
add(new JSeparator(SwingConstants.VERTICAL), "West");
//выводим окно на экран
setSize(300, 200); setVisible(true);
}

Меню и панели инструментов |
269 |
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new CascadedMenus(); } });
}
}
В этом примере мы создали весьма правдоподобное меню, которое содержит команды для работы с простым текстовым редактором. Для получения каскадных меню несколько выпадающих меню были просто вложены друг в друга в нужном порядке. Заодно мы еще раз использовали разделитель, который акцентировал различие между командами для выбора стиля текста и для выбора шрифта для него.
Посмотрите, как можно использовать разделитель вне меню. В примере мы создали вертикальный разделитель (для этого есть специальный конструктор), который добавили на запад своей панели содержимого. Получилось весьма неплохое украшение для возможного интерфейса. Правда, для того чтобы в печатной книге его было лучше видно, мы дополнительно отделили разделитель от границ окна с помощью рамки с пустым пространством EmptyBorder.
В примере была создана очень простая система меню, обычно она в несколько раз больше и сложнее. Но даже для такой простой системы код получился немного неряшливым (чего стоят одни только добавления одного в другого, а другого в третье). Действительно, инструкции создания меню в коде сильно «загрязняют» последний, это хорошо известный факт, что «декларативный» стиль описания объекта не слишком элегантно выглядит через призму вызовов объектно-ориентированных методов. Мы ведь просто хотим указать структуру меню, а не вызываем никаких действий. Чуть позже мы попытаемся решить эту проблему.
Клавиатурные сокращения и мнемоники
Если часто работаешь с одним и тем же приложением, и раз за разом выполняешь одни и те же операции, поневоле хочется повысить свою производительность и не тратить время на то, что делалось уже много раз. Именно для этого в графических приложениях и появились клавиатурные сокращения, или клавиши быстрого доступа (accelerators) и мнемоники (mnemonics). Опытные пользователи высоко ценят возможность выполнить операцию, требующую в обычных условиях долгих «блуж-
270 |
ГЛАВА 10 |
даний» по системе меню, просто нажав комбинацию из двух-трех клавиш. К тому же, как мы уже отмечали, еще больше повышают привлекательность вашего приложения мнемоники, помогая пользователям компьютеров без мыши и пользователям с ограниченными возможностями.
Если с мнемониками все более или менее ясно (мы уже встречались с ними в главах 8 и 9), то клавиатурные сокращения — привилегия элементов меню, где они чрезвычайно полезны. Клавиатурное сокращение — это произвольная комбинация всевозможных управляющих клавиш (Shift, Ctrl, Alt) с клавишей некоторого латинского символа, нажатие которой эквивалентно выбору элемента меню, что освобождает пользователя от необходимости разыскивать этот элемент в системе меню.
Вообще говоря, как мнемоники, так и клавиши быстрого доступа очень важны для меню. В хорошем приложении абсолютно все элементы меню, а также выпадающие и вложенные меню должны обладать мнемониками, а наиболее часто используемые команды — уникальной клавиатурной комбинацией. Попробуем создать такую «образцовую» систему меню:
//GoodMenu.java
//Клавиатурные комбинации и мнемоники для меню Swing import javax.swing.*;
import java.awt.event.*; import java.awt.*;
public class GoodMenu extends JFrame { public GoodMenu() {
super("GoodMenu"); setDefaultCloseOperation( EXIT_ON_CLOSE );
//создаем строку главного меню
JMenuBar menuBar = new JMenuBar();
//некоторые весьма часто встречающиеся
//выпадающие меню menuBar.add(createFileMenu()); menuBar.add(createEditMenu());
//поместим меню в наше окно setJMenuBar(menuBar);
//выводим окно на экран
setSize(300, 200); setVisible(true);
}
// создает меню "Файл"
private JMenu createFileMenu() { // выпадающее меню
JMenu file = new JMenu("Файл"); file.setMnemonic('Ф');
// пункт меню "Открыть"
Меню и панели инструментов |
271 |
JMenuItem open = new JMenuItem("Открыть"); open.setMnemonic('О'); // русская буква
//установим клавишу быстрого доступа (латинская буква) open.setAccelerator(
KeyStroke.getKeyStroke('O', KeyEvent.CTRL_MASK));
//пункт меню "Сохранить"
JMenuItem save = new JMenuItem("Сохранить"); save.setMnemonic('С');
save.setAccelerator(
KeyStroke.getKeyStroke('S', KeyEvent.CTRL_MASK)); // добавим все в меню
file.add(open); file.add(save); return file;
}
// создает меню "Правка" private JMenu createEditMenu() {
// выпадающее меню
JMenu edit = new JMenu("Правка"); edit.setMnemonic('П');
// пункт меню "Вырезать"
JMenuItem cut = new JMenuItem("Вырезать"); cut.setMnemonic('В');
cut.setAccelerator(
KeyStroke.getKeyStroke('X', KeyEvent.CTRL_MASK)); // пункт меню "Копировать"
JMenuItem copy = new JMenuItem("Копировать"); copy.setMnemonic('К');
//клавишу быстрого доступа можно создать и так copy.setAccelerator(KeyStroke.getKeyStroke("ctrl C"));
//готово
edit.add(cut);
edit.add(copy); return edit;
}
public static void main(String[] args) { SwingUtilities.invokeLater(
new Runnable() {
public void run() { new GoodMenu(); } });
}
}

272 |
ГЛАВА 10 |
В этом примере создается небольшое меню с пунктами Файл и Правка, которые настолько часто встречаются, что стали уже стандартными. Для них мы, не жалея времени и усилий, сделали все необходимые настройки, а именно установили мнемоники и клавиатурные комбинации.
Вы можете видеть, что мнемоники устанавливаются хорошо знакомым нам методом setMnemonic(), а вот клавиши быстрого доступа для элементов меню создаются с помощью класса KeyStroke. В этом классе определено несколько перегруженных статических методов вида getKeyStroke(), которые возвращают экземпляр нужной клавиатурной комбинации. В примере демонстрируются два варианта этого метода: один требует указания символа для клавиши быстрого доступа и набора управляющих клавиш (вы можете указать несколько управляющих клавиш, используя соответствующие константы из класса KeyEvent, связав их операцией поразрядного «ИЛИ»), а другой позволяет получить нужное клавиатурное сокращение с помощью строки, в которой сначала указывают псевдонимы управляющих клавиш, а затем символ клавиши быстрого доступа.
Запустив программу с примером, вы столкнетесь с досадной проблемой мнемоник. Если вы уже находитесь в меню, то есть оно получило фокус ввода, мнемоники с русскими символами работают. Но если вы хотите получить доступ к одному из выпадающих меню первый раз (меню не обладает фокусом ввода), мнемоники не работают (меню файл не активируется с помощью клавиш Alt-Ф). Какие бы сочетания клавиш мы не нажимали, это не поможет, и меню так и не получит фокус ввода. Это -- большой недостаток, и избежать его можно, либо включив в русские названия элементов меню латинские буквы, либо заранее предупредив пользователя о том, что строка меню в Swing получает фокус ввода c помощью клавиши F10 (по сути, она считается стандартной клавишей перехода на строку меню, но часто ли ее используют? Я не припомню, чтобы мне приходилось нажимать ее вместо мнемоники).
Но, как бы там ни было, клавиши быстрого доступа работают исправно, и довольно значительно повышают привлекательность приложения, так что не забывайте про них. Главное, помните о том, что в мире интерфейсов устоялись свои стандарты, пусть и молодые, и не стоит заново изобретать альтернативу старым добрым Control-S и Control-С, пусть они и набили уже всем оскомину. Даже на русском языке эти сочетания остаются собой. Для всех важных и частых операций своего приложения продумывайте наличие эффективных клавиш быстрого доступа1.
1 Программистам прекрасно знакомы клавиши быстрого доступа и их значение. Ни один потрясающей красоты дизайн не заменит возможности быстрым нажатием провернуть одну из множества нужных в данный момент операций, не отвлекаясь на их поиск в меню. Иногда список быстрых клавиш не умещается на нескольких страницах руководства средства разработки, такого как IDEA.
Меню и панели инструментов |
273 |
Всплывающие меню JPopupMenu
Меню не обязательно относятся ко всему приложению, они могут пригодиться и для частей этого приложения (например, для текстового поля в редакторе). В таком случае меню появляется на экране только по желанию пользователя, поэтому их называют всплывающими (popup menu), или контекстными (context-sensitive menu). Пользователь вызывает это меню, чтобы получить список команд для того объекта приложения, с которым он работает, не тратя время на поиск этих команд в главном меню.
Всплывающие меню в Swing реализованы классом JPopupMenu. На самом деле вы уже видели этот класс «в деле» — именно он применяется в выпадающем меню JMenu для вывода его элементов. Но использовать его можно и отдельно, в чем мы сейчас убедимся:
//PopupMenus.java
//Работа с всплывающими меню import javax.swing.*;
import java.awt.*;
public class PopupMenus extends JFrame { public PopupMenus() {
super("PopupMenus"); setDefaultCloseOperation( EXIT_ON_CLOSE );
//получаем всплывающее меню
JPopupMenu popup = createPopupMenu();
//и привязываем к нашей панели содержимого
((JComponent)getContentPane()). setComponentPopupMenu(popup);
//"прозрачная" для меню кнопка
JButton button = new JButton("Проба пера"); button.setInheritsPopupMenu(true); add(button, "South");
// выводим окно на экран setSize(300, 200); setVisible(true);
}
// создаем наше всплывающее меню private JPopupMenu createPopupMenu() {
//создаем само всплывающее меню
JPopupMenu pm = new JPopupMenu();
//создаем его пункты
JMenuItem good = new JMenuItem("Отлично");
JMenuItem excellent = new JMenuItem("Замечательно"); // и добавдяем все тем же методом add() pm.add(good);

274 |
ГЛАВА 10 |
|
pm.add(excellent); |
|
return pm; |
|
} |
|
public static void main(String[] args) { |
|
SwingUtilities.invokeLater( |
|
new Runnable() { |
|
public void run() { new PopupMenus(); } }); |
} |
} |
|
В этом примере мы создаем простейшее всплывающее меню из двух элементов; это делает метод createPopupMenu(). Как видно, все действия аналогичны тем, что мы делали при создании выпадающих меню JMenu. После того как всплывающее меню подготовлено к работе, необходимо определить, где и при каком условии нужно вывести его на экран. Так как на различных платформах способ вызова всплывающего меню может быть разным, момент, когда это необходимо сделать, определяет в Swing текущий менеджер внешнего вида и поведения2. Ну а само меню задается для конкретного компонента, методом setComponentPopupMenu(). В примере мы задаем меню сразу же для всей панели содержимого нашего окна. Данный метод находится в базовом классе библиотеки JComponent, и таким образом доступен для всех компонентов. Есть еще один интересный метод для всплывающих меню с названием setInheritsPopupMenu(). Это булево свойство, по умолчанию отключенное (равное false). Означает оно, что компонент будет унаследовать всплывающее меню от своего компонента-предка (как правило контейнера). В нашем примере кнопка унаследует всплывающее меню от панели содержимого, в чем вы сможете легко убедиться, запустив программу.
Для вывода меню на экран чаще всего поступают так, как сделано у нас в примере. Однако есть и более низкоуровневый способ — создать в нужном компоненте слушателя событий от мыши, и при щелчке (обычно правой кнопкой) вывести всплывающее меню в месте этого щелчка.
Для вывода меню на экран в класс JPopupMenu специально был добавлен перегруженный метод show(), который позволяет одновременно указать, где (в каких координатах) и в каком компоненте будет показано меню. Особенно это пригодится в сложных случаях, когда у компонента может быть разное меню в зависимости от точки, в которой меню было вызвано. В общем же случае определять, как меню появится на экране, лучше доверить менеджеру внешнего вида.
Реализовано всплывающее меню в Swing достаточно разумно. В обычной ситуации оно представляет собой легковесный компонент, унаследованный от базового класса
2 Для большинства платформ это щелчок правой кнопкой мыши или сочетание клавиш ShiftF10. Интересно, что сочетание клавиш вызовет меню для компонента, обладающего фокусом ввода, а иногда требуется вызывать его и для не способных обладать фокусом компонентов.