Материал: Портянкин И. Swing

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

Меню и панели инструментов

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

}

Источник: https://studfile.net/preview/15935977/