Доброго времени суток, дорогие хабравчане.
Недавно столкнулся с проблемой создания панели меню — стандартной панели, которая присутствует почти в каждом мало-мальски функциональном UI. И не с той проблемой, что ее сложно создать, напротив, в Swing создание UI занятие довольно тривиальное, большинство элементов интерфейса создаются в пару строк кода (это в равной степени касается и меню). А с проблемой создания многофункционального, сложного меню, которое выливается в невероятное количество кода, который в последствии не то чтобы сопровождать, а порой даже и читать невозможно.
Для хранения всего процесса генерации меню любой сложности, решил я прибегнуть к силе под названием XML. С его помощью можно описать меню, как мы хотим его видеть, а благодаря классу-утилите, который не преминем создать, наш XML будет распарсен и в итоге можно будет получить то, ради чего все это и затевается — меню любой сложности. Если вас это заинтересовало, добро пожаловать под хаброкат...
Итак, для начала опишем меню, которое мы хотим привязать к нашему UI. В качестве примера я решил использовать немного урезанное меню, в основу которого легло меню, которое многие из нас наблюдали в Google Docs (да простит меня Google):
Помимо структуры, у некоторых из описанных элементов будет картинка, клавиатурное сокращение и мнемоника. Вот такое незамысловатое меню и попробуем создать.
Перед началом создания XML файла, описывающее наше, частично заимствованное (только в качестве примера), красивое меню, определимся какие элементы в нем присутствуют.
В первую очередь это JMenuBar, панель на которой будут располагаться описанные ниже элементы. И вот эту панель мы и будем вытаскивать после обработки XML.
Далее у нас следуют представители JMenu — меню и подменю собственной персоной (File, Edit, Paragraph Styles и т.д.). А также JMenuItem — элементы всех меню и подменю (Open, Copy, Paste, Report abuse и т.д.). И конечно же разделители (сепараторы) — горизонтальные линии, с помощью которых элементы меню объединяются в логические группы.
У каждого элемента есть еще ряд свойств, таких как — имя элемента (по нему мы сможем обратиться к интересующему нас элементу из кода), текст, наличие картинки возле текста, комбинация клавиш, по которой можно сфокусироваться на элементе с помощью клавиатуры (т.н. мнемоники — отображаются в виде подчеркнутого элемента в имени и вызываются комбинацией alt + выделенный_символ_в_имени. Например, alt+F, для открытия меню File) и клавиатурное сокращение (т.н. акселератор — отображается в виде комбинации клавиш рядом с элементом меню для быстрого его использования, без вхождения в само меню. Например, Ctrl+O для открытия файла). Также элемент может быть активным и неактивным (Edit -> Cut, если не выделен текст).
С кирпичиками, из которых состоит меню, ознакомились. Осталось рассмотреть XML теги и имена свойств, которые будут соответствовать элементам, описанным выше.
Теги: JMenuBar — <menubar>, JMenu — <menu>, JMenuItem — <menuitem>, разделитель — <separator/>
Свойства: имя — name, текст — text, картинка — image, мнемоника — mnemonic, акселератор — accelerator, доступность элемента — isEnabled.
Определившись с XML элементами нашего меню, можно незамедлительно приступить к описанию меню, которое мы хотим в итоге получить:
Теперь, когда мы описали наше меню в рамках XML, осталось его только распарсить. Для работы с нашей XML воспользуемся Java пакетом org.xml.sax (с API можно ознакомиться здесь и здесь). За обработку элементов нашей XML структуры будет отвечать класс XMLMenuHandler, расширяющий класс DefaultHandler, который в свою очередь реализует интерфейс ContentHandler и ряд других, не менее полезных интерфейсов:
Опишем класс, который будет отвечать за загрузку XML и активно использовать наш обработчик:
А теперь посмотрим на проделанную работу в деле — применим описанное в XML меню к простенькому UI:
Запустив наше приложение, мы получим примерно следующее окошко с меню (сруктура которого такая же, как было описанно выше):

Теперь, если появится необходимость в построении ветвистого меню, все что необходимо будет сделать, так это создать соответствующий XML файл, в котором будут описаны все элементы строки меню с их свойствами, а также подключить к проекту два класса XMLMenuHandler и XMLMenuParser (можно даже их объединить, сделав XMLMenuHandler внутренним классом) и использовать этот суповой набор, как было показано выше в примере.
Спасибо за внимание и поздравляю с прошедшим днем программиста.
Исходники проекта можно взять тут: http://www.google.com/url?q=https%3A%2F%2Fdocs.google.com%2Fopen%3Fid%3D0B1I02TclzMD_RTVtNVFNVkkxU1U
Недавно столкнулся с проблемой создания панели меню — стандартной панели, которая присутствует почти в каждом мало-мальски функциональном UI. И не с той проблемой, что ее сложно создать, напротив, в Swing создание UI занятие довольно тривиальное, большинство элементов интерфейса создаются в пару строк кода (это в равной степени касается и меню). А с проблемой создания многофункционального, сложного меню, которое выливается в невероятное количество кода, который в последствии не то чтобы сопровождать, а порой даже и читать невозможно.
Для хранения всего процесса генерации меню любой сложности, решил я прибегнуть к силе под названием XML. С его помощью можно описать меню, как мы хотим его видеть, а благодаря классу-утилите, который не преминем создать, наш XML будет распарсен и в итоге можно будет получить то, ради чего все это и затевается — меню любой сложности. Если вас это заинтересовало, добро пожаловать под хаброкат...
Итак, для начала опишем меню, которое мы хотим привязать к нашему UI. В качестве примера я решил использовать немного урезанное меню, в основу которого легло меню, которое многие из нас наблюдали в Google Docs (да простит меня Google):
- File -> New -> Список документов для создания
- File -> Open
- File -> Download as -> варианты закачки
- File -> Print
- Edit -> Undo
- Edit -> Redo
- Edit -> Cut
- Edit -> Copy
- Edit -> Paste
- Format -> Paragraph Styles -> Normal Text -> варианты формата обычного текста
- Format -> Paragraph Styles -> Heading -> варианты формата заголовка
- Format -> Paragraph Styles -> Options -> разные настройки стилей
- Help -> Report an issue
- Help -> Report abuse
- Help -> Keyboard shortcuts
Помимо структуры, у некоторых из описанных элементов будет картинка, клавиатурное сокращение и мнемоника. Вот такое незамысловатое меню и попробуем создать.
Описание элементов меню в формате XML
Перед началом создания XML файла, описывающее наше, частично заимствованное (только в качестве примера), красивое меню, определимся какие элементы в нем присутствуют.
В первую очередь это JMenuBar, панель на которой будут располагаться описанные ниже элементы. И вот эту панель мы и будем вытаскивать после обработки XML.
Далее у нас следуют представители JMenu — меню и подменю собственной персоной (File, Edit, Paragraph Styles и т.д.). А также JMenuItem — элементы всех меню и подменю (Open, Copy, Paste, Report abuse и т.д.). И конечно же разделители (сепараторы) — горизонтальные линии, с помощью которых элементы меню объединяются в логические группы.
У каждого элемента есть еще ряд свойств, таких как — имя элемента (по нему мы сможем обратиться к интересующему нас элементу из кода), текст, наличие картинки возле текста, комбинация клавиш, по которой можно сфокусироваться на элементе с помощью клавиатуры (т.н. мнемоники — отображаются в виде подчеркнутого элемента в имени и вызываются комбинацией alt + выделенный_символ_в_имени. Например, alt+F, для открытия меню File) и клавиатурное сокращение (т.н. акселератор — отображается в виде комбинации клавиш рядом с элементом меню для быстрого его использования, без вхождения в само меню. Например, Ctrl+O для открытия файла). Также элемент может быть активным и неактивным (Edit -> Cut, если не выделен текст).
С кирпичиками, из которых состоит меню, ознакомились. Осталось рассмотреть XML теги и имена свойств, которые будут соответствовать элементам, описанным выше.
Теги: JMenuBar — <menubar>, JMenu — <menu>, JMenuItem — <menuitem>, разделитель — <separator/>
Свойства: имя — name, текст — text, картинка — image, мнемоника — mnemonic, акселератор — accelerator, доступность элемента — isEnabled.
Знакомьтесь, XML, описывающий меню!
Определившись с XML элементами нашего меню, можно незамедлительно приступить к описанию меню, которое мы хотим в итоге получить:
<?xml version="1.0" encoding="utf-8"?> <menubar name="ourMenu"> <!--File--> <menu name="file" text="File" mnemonic="F"> <menu name="new" text="New" mnemonic="N"> <menuitem name="doc" text="Document" image="./data/MenuXML/img1.jpg"/> </menu> <menuitem name="open" text="Open..." mnemonic="O" accelerator="control O"/> <separator/> <menu name="download" text="Download as"> <menuitem name="msword" text="Microsoft Word (.docx)"/> </menu> <separator/> <menuitem name="print" text="Print" mnemonic="P" accelerator="control P"/> </menu> <!--Edit--> <menu name="edit" text="Edit" mnemonic="d"> <menuitem name="undo" text="Undo" image="./data/MenuXML/img1.jpg" accelerator="control Z" isEnabled="false"/> <menuitem name="redo" text="Redo" image="./data/MenuXML/img1.jpg" accelerator="control Y" isEnabled="false"/> <separator/> <menuitem name="cut" text="Cut" image="./data/MenuXML/img1.jpg" accelerator="control X" isEnabled="false"/> <menuitem name="copy" text="Copy" image="./data/MenuXML/img1.jpg" accelerator="control C" isEnabled="false"/> <menuitem name="paste" text="Paste" image="./data/MenuXML/img1.jpg" accelerator="control V" isEnabled="false"/> </menu> <!--Format--> <menu name="format" text="Format" mnemonic="o"> <menu name="styles" text="Paragraph styles"> <menu name="normalText" text="Normal text"> <menuitem name="applyNormText" text="Apply normal text" accelerator="control alt 0"/> </menu> <menu name="heading" text="Heading"> <menuitem name="applyHeading" text="Apply heading" accelerator="control alt 1"/> </menu> <separator/> <menu name="options" text="Options" image="./data/MenuXML/img1.jpg"> <menuitem name="saveOptions" text="Save options"/> </menu> </menu> </menu> <!--Help--> <menu name="help" text="Help" mnemonic="H"> <menuitem name="issue" text="Report an issue"/> <menuitem name="abuse" text="Repor abuse"/> <separator/> <menuitem name="shortcuts" text="Keyboard shortcuts" accelerator="control SLASH"/> </menu> </menubar>
XML-обработчик
Теперь, когда мы описали наше меню в рамках XML, осталось его только распарсить. Для работы с нашей XML воспользуемся Java пакетом org.xml.sax (с API можно ознакомиться здесь и здесь). За обработку элементов нашей XML структуры будет отвечать класс XMLMenuHandler, расширяющий класс DefaultHandler, который в свою очередь реализует интерфейс ContentHandler и ряд других, не менее полезных интерфейсов:
public class XMLMenuHandler extends DefaultHandler { private Map menuMap = new HashMap(); private JMenuBar menuBar; private LinkedList menuList = new LinkedList(); @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { //Определяем стартовый элемент (menu, menubar, menuitem или separator) if(qName.equals("menu")) { parseMenu(atts); } else if(qName.equals("menubar")) { parseMenubar(atts); } else if(qName.equals("menuitem")) { parseMenuitem(atts); } else if(qName.equals("separator")) { parseSeparator(); } } //Определяем закрывающий тег </menu> для перехода к следующему элементу меню. @Override public void endElement(String uri, String localName, String qName) throws SAXException { if(qName.equals("menu")) { menuList.removeFirst(); } } private void parseSeparator() { //Добавляем разделитель в текущее меню ((JMenu)menuList.getFirst()).addSeparator(); } private void parseMenuitem(Attributes atts) { //Создаем пункт меню JMenuItem menuItem = new JMenuItem(); String menuItemName = atts.getValue("name"); //Свойства пункта меню parseAttributes(menuItem, atts); menuMap.put(menuItemName, menuItem); //Добавляем к текущему выпадающему меню ((JMenu)menuList.getFirst()).add(menuItem); } private void parseMenubar(Attributes atts) { //Создаем строку меню JMenuBar tempMenuBar = new JMenuBar(); String menuBarName = atts.getValue("name"); menuMap.put(menuBarName, tempMenuBar); menuBar = tempMenuBar; } private void parseMenu(Attributes atts) { //Создаем меню JMenu menu = new JMenu(); String menuName = atts.getValue("name"); //Свойства меню parseAttributes(menu, atts); menuMap.put(menuName, menu); //Создаем меню, если нет ни одного меню-элемента. if (menuList.size() == 0) { menuBar.add(menu); } else { //Добавляем подменю ((JMenu)menuList.getFirst()).add(menu); } //Добавляем меню к остальным меню-элементам menuList.addFirst(menu); } private void parseAttributes(JMenuItem item, Attributes atts) { //Получаем аттрибуты String text = atts.getValue("text"); String image = atts.getValue("image"); String mnemonic = atts.getValue("mnemonic"); String accelerator = atts.getValue("accelerator"); String isEnabled = atts.getValue("isEnabled"); //Настраиваем свойства меню в соответствие с описанными аттрибутами в XML item.setText(text); if(image != null) { item.setIcon(new ImageIcon(image)); } if(mnemonic != null) { item.setMnemonic(mnemonic.charAt(0)); } if(accelerator != null) { item.setAccelerator(KeyStroke.getKeyStroke(accelerator)); } if(isEnabled != null) { item.setEnabled(Boolean.parseBoolean(isEnabled)); } } public Map getMenuMap() { return menuMap; }
Опишем класс, который будет отвечать за загрузку XML и активно использовать наш обработчик:
public class XMLMenuParser { //XML, который будем вычитывать private InputSource inputSource; //XML парсер private SAXParser saxParser; //Наш XML обработчик private XMLMenuHandler xmlMenuHandler; public XMLMenuParser(InputStream inputStream) { //Загружаем XML файл и инициализируем XML обработчик и парсер. try { Reader reader = new InputStreamReader(inputStream); inputSource = new InputSource(reader); saxParser = SAXParserFactory.newInstance().newSAXParser(); xmlMenuHandler = new XMLMenuHandler(); } catch (Exception e) { System.out.println("Something went wrong during SAXParser initialization: " + e.getMessage()); throw new RuntimeException(e); } } public void parseXML() throws IOException, SAXException { saxParser.parse(inputSource, xmlMenuHandler); } public JMenuBar getMenuBar(String name) { return (JMenuBar)xmlMenuHandler.getMenuMap().get(name); } }
А теперь посмотрим на проделанную работу в деле — применим описанное в XML меню к простенькому UI:
public class XMLMenuCreator extends JFrame { public XMLMenuCreator() { super("XML Menu Creator"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); try { FileInputStream in = new FileInputStream("./data/MenuXML/menu.xml"); XMLMenuParser xmlParser = new XMLMenuParser(in); xmlParser.parseXML(); //Вытаскиваем, сформированное из XML файла меню, по его имени (тег <menubar>), //и прикрепляем к нашему UI setJMenuBar(xmlParser.getMenuBar("ourMenu")); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); } setSize(200, 200); setVisible(true); } }
Запустив наше приложение, мы получим примерно следующее окошко с меню (сруктура которого такая же, как было описанно выше):

Заключение
Теперь, если появится необходимость в построении ветвистого меню, все что необходимо будет сделать, так это создать соответствующий XML файл, в котором будут описаны все элементы строки меню с их свойствами, а также подключить к проекту два класса XMLMenuHandler и XMLMenuParser (можно даже их объединить, сделав XMLMenuHandler внутренним классом) и использовать этот суповой набор, как было показано выше в примере.
Спасибо за внимание и поздравляю с прошедшим днем программиста.
Исходники проекта можно взять тут: http://www.google.com/url?q=https%3A%2F%2Fdocs.google.com%2Fopen%3Fid%3D0B1I02TclzMD_RTVtNVFNVkkxU1U
