Меню и панели инструментов |
275 |
JComponent, и для вывода на экран его просто делают видимым и добавляют в специально предназначенный для этого слой POPUP_LAYER многослойной панели JLayeredPane. Однако как только всплывающее меню начинает «вылезать» за границы окна, в котором оно должно быть показано, или используется в AWT-приложении, все меняется — всплывающее меню выводится в собственном отдельном тяжеловесном окне без рамки. Для программиста вся эта кухня остается незаметной, что, конечно, облегчает программирование. Впрочем, вы можете включиться в процесс и с помощью метода setLightweightPopupEnabled() рекомендовать для всплывающего меню тип компонента (легковесный или тяжеловесный). Иногда это может быть полезно.
Загрузка меню из файлов XML
Во всех примерах с меню, что мы разобрали, можно было заметить весьма неприятную тенденцию: создание нескольких полноценных пунктов меню (со значками, мнемониками, акселераторами и обработчиками событий) сильно раздувало код наших программ. Можно себе представить, насколько сильно разрастется код программы, обладающей более или менее большими возможностями, и соответственно большой системой меню. Выходом из этой ситуации может стать использование архитектуры Action, встроенной во все кнопки библиотеки, и такое решение будет действительно хорошим, особенно если эти же команды будут использоваться и для создания кнопок на панели инструментов, и для возможных контекстных меню. Но и у этого подхода есть свои недостатки — для каждой команды придется писать довольно объемный класс, и таких классов-близнецов может быть очень много.
С другой стороны, существует множество визуальных инструментов для построения пользовательского интерфейса, и в них почти всегда есть средства для создания меню произвольной сложности. Хотя после их использования качество и чистота кода оставляют желать лучшего, а стоимость поддержки программы возрастает (а иногда вообще невозможна без примененного визуального средства), в случае с меню они существенно сэкономят ваше время.
Наконец, можно просто вспомнить о том, что система меню представляет собой несложную иерархическую структуру, в которой в качестве узлов выступают выпадающие меню JMenu, листьями являются пункты меню, такие как JMenuItem и JRadioButtonMenuItem, а корнем всей системы является строка меню JMenuBar. У каждого члена этой иерархии есть некоторые атрибуты: название, значок, текст и т.п. Не правда ли, описанная структура так и просится в файл формата XML? Именно формат XML предназначен для описания произвольных иерархических структур, узлы которых могут иметь набор некоторых атрибутов. Придумав подходящее описание XML для меню Swing, а затем написав специальный инструмент для загрузки, мы сможем мгновенно загружать самые запутанные и сложные меню. Кстати, подобный подход часто используется во многих графических системах: меню описывается на некотором текстовом языке и помещается в «ресурсы» программы, откуда загрузить его не составляет особого труда.
Прежде всего необходимо определить то, как будет выглядеть файл XML с описанием системы меню. Наиболее логичным смотрится следующий вариант:
<?xml version="1.0" encoding="UTF-8"?>
<menubar name="mainMenu">
<menu name="file" text="Файл" mnemonic="Ф">
<menuitem name="create" text="Создать" mnemonic="Н" accelerator="control N"/>
<menuitem name="open" text="Открыть" mnemonic="О"
276 ГЛАВА 10
enabled="false"/>
<menuitem name="close" text="Закрыть" mnemonic="З" enabled="false"/>
<menuitem name="separator"/>
<menu name="print" text="Печать" mnemonic="П"> <menuitem name="preview"
text="Предварительный просмотр" mnemonic="р"/>
</menu>
<menuitem name="separator"/>
<menuitem name="exit" label="Выход" mnemonic="В" accelerator="alt X"/>
</menu>
</menubar>
После стандартного заголовка файла XML следует описание элементов нашей системы меню. Корнем системы, как и следует ожидать, стал элемент с названием menubar: он будет описывать строку меню, в которой затем разместятся все выпадающие меню и пункты меню. У элемента menubar имеется только один атрибут name (имя), по этому имени мы затем сможем найти его3. Чтобы при поиске элементов меню не возникало ошибок, имена всех элементов должны быть уникальными. К строке меню присоединяются выпа-
дающие меню: для XML это означает наличие элементов-потомков с названием menu. Как правило, для выпадающих меню настраивается два свойства: text (текст меню) и mnemonic
(символ мнемоники). Данные свойства будут задаваться в виде атрибутов элемента menu. Выпадающие меню состоят из пунктов меню и, если это необходимо, из других выпадаю-
щих меню (это позволит организовать сложную иерархическую систему меню). Поэтому потомками элемента menu смогут быть элементы menuitem (пункт меню) или другие элементы menu. Больше всего атрибутов поддерживает элемент menuitem: для него часто задаются не только текст меню и символ мнемоники, но и сочетание клавиш быстрого доступа (accelerator)4, а также то, доступен ли он в данный момент пользователю (enabled). Кроме того, в качестве особых элементов могут выступать разделители меню. Для них мы зарезервировали специальное имя separator. При написании файла XML, описывающего меню, не забывайте о том, что все элементы XML обязательно должны закрываться. Для системы меню это имеет дополнительное значение: пока вы не закроете элемент menu, все следующие за ним пункты меню будут попадать в описываемое им выпадающее меню.
Созданное нами описание системы меню на языке XML интуитивно понятно и позволяет мгновенно описать сложную систему меню, «на лету» задавая наиболее важные свойства пунктов меню. Теперь нам необходимо написать инструмент, распознающий подобный файл XML и создающий на его основе меню Swing. Стандартные библиотеки Java содержат все необходимые инструменты для работы с XML, в том числе и упрощенный интерфейс SAX (Simple API for XML). Именно этот интерфейс мы применим для распознавания системы меню. Вот что у нас получится:
// com/porty/swing/XMLMenuLoader.java
3 Здесь может показаться, что имя для строки меню ни к чему, ведь она в нашем файле описана одна. Однако чуть позже мы увидим, что добавить можно сколь угодно строк меню, так что имя все же нужно.
4 Нам не составит труда распознать строку, описывающую клавиши быстрого доступа: в классе KeyStroke имеется перегруженный метод getKeyStroke(), позволяющий получить клавиатурное сокращение на основе строки, составленной по определенным правилам. Описание этих правил вы сможете найти в интерактивной документации класса KeyStroke.
Меню и панели инструментов |
277 |
// Инструмент для загрузки меню из файла XML package com.porty.swing;
import javax.swing.*; import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler; import java.io.*;
import javax.xml.parsers.*; import java.awt.event.*; import java.util.*;
public class XMLMenuLoader {
//источник данных XML private InputSource source;
//анализатор XML
private SAXParser parser; // обработчик XML
private DefaultHandler documentHandler;
//хранилище для всех частей системы меню private Map<String, JComponent> menuStorage
=new HashMap<String, JComponent>();
//конструктор, требует задать поток данных с меню public XMLMenuLoader(InputStream stream) {
//настраиваем источник данных XML try {
Reader reader = new InputStreamReader(stream, "UTF-8"); source = new InputSource(reader);
parser = SAXParserFactory. newInstance().newSAXParser();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
//создаем обработчик XML
documentHandler = new XMLParser();
}
// считывает XML и создает систему меню public void parse() throws Exception { parser.parse(source, documentHandler);
}
278 ГЛАВА 10
// позволяет получить строку меню
public JMenuBar getMenuBar(String name) { return (JMenuBar) menuStorage.get(name);
}
//позволяет получить выпадающее меню public JMenu getMenu(String name) {
return (JMenu) menuStorage.get(name);
}
//позволяет получить элемент меню
public JMenuItem getMenuItem(String name) { return (JMenuItem) menuStorage.get(name);
}
//удобный метод для быстрого добавления
//слушателя событий
public void addActionListener(String name, ActionListener listener) {
getMenuItem(name).addActionListener(listener);
}
// текущая строка меню
private JMenuBar currentMenuBar;
// список для упорядочения выпадающих меню
private LinkedList<JMenu> menus = new LinkedList<JMenu>();
// обработчик XML
class XMLParser extends DefaultHandler { // новый узел XML
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
// определяем тип узла
if (qName.equals("menubar")) parseMenuBar(attributes);
else if (qName.equals("menu")) parseMenu(attributes);
else if (qName.equals("menuitem")) parseMenuItem(attributes);
}
// конец узла, используется для смены выпадающих меню public void endElement(String uri, String localName,
String qName) {
if (qName.equals("menu")) menus.removeFirst();
}
Меню и панели инструментов |
279 |
// создает новую строку меню
protected void parseMenuBar(Attributes attrs) { JMenuBar menuBar = new JMenuBar();
// определяем имя
String name = attrs.getValue("name"); menuStorage.put(name, menuBar); currentMenuBar = menuBar;
}
// создает новое выпадающее меню
protected void parseMenu(Attributes attrs) { // создаем меню
JMenu menu = new JMenu();
String name = attrs.getValue("name");
//настраиваем общие атрибуты adjustProperties(menu, attrs); menuStorage.put(name, menu);
//добавляем меню к предыдущему выпадающему
//меню или к строке меню
if ( menus.size() != 0 ) { menus.getFirst().add(menu);
} else { currentMenuBar.add(menu);
}
// добавляем в список выпадающих меню menus.addFirst(menu);
}
// новый пункт меню
protected void parseMenuItem(Attributes attrs) {
//проверяем, не разделитель ли это
String name = attrs.getValue("name"); if (name.equals("separator")) {
menus.getFirst().addSeparator(); return;
}
//создаем пункт меню
JMenuItem menuItem = new JMenuItem();
//настраиваем свойства adjustProperties(menuItem, attrs); menuStorage.put(name, menuItem);
//добавляем к текущему выпадающему меню menus.getFirst().add(menuItem);
}