Меню
Каждый компонент способен содержать меню, включая JApplet, JFrame, JDialog и их потомков, имеющих метод setJMenuBar( ), который принимает JMenuBar (вы можете иметь только один JMenuBar для определенного компонента). Вы добавляете JMenu в JMenuBar, и JMenuItem в JMenu. Каждый JMenuItem может иметь присоединенный к нему ActionListener для сигнализации, что элемент меню выбран.
В отличие от систем, использующих ресурсы, в Java и Swing вы должны в ручную собрать все меню в исходном коде. Вот очень простой пример меню:
//: c13:SimpleMenus.java
// <applet code=SimpleMenus
// width=200 height=75> </applet>
import javax.swing.*; import java.awt.event.*; import java.awt.*; import com.bruceeckel.swing.*;
public class SimpleMenus extends JApplet { JTextField t = new JTextField(15); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e){ t.setText( ((JMenuItem)e.getSource()).getText()); } }; JMenu[] menus = { new JMenu("Winken"), new JMenu("Blinken"), new JMenu("Nod") }; JMenuItem[] items = { new JMenuItem("Fee"), new JMenuItem("Fi"), new JMenuItem("Fo"), new JMenuItem("Zip"), new JMenuItem("Zap"), new JMenuItem("Zot"), new JMenuItem("Olly"), new JMenuItem("Oxen"), new JMenuItem("Free") }; public void init() { for(int i = 0; i < items.length; i++) { items[i].addActionListener(al); menus[i%3].add(items[i]); } JMenuBar mb = new JMenuBar(); for(int i = 0; i < menus.length; i++) mb.add(menus[i]); setJMenuBar(mb); Container cp = getContentPane(); cp.setLayout(new FlowLayout()); cp.add(t); } public static void main(String[] args) { Console.run(new SimpleMenus(), 200, 75); } } ///:~
Использование оператора остатка от деления в выражении “i%3” распределяет элементы меню на три JMenu. Каждый JMenuItem должен иметь присоединенный к нему ActionListener; здесь один и тот же ActionListener используется везде, но вам обычно нужны индивидуальные слушатели для каждого JMenuItem.
JMenuItem наследует от AbstractButton, так что он имеет поведение, аналогичное кнопке. Сам по себе он обеспечивает элемент, который может быть помещен в выпадающее меню. Есть также три типа наследников от JMenuItem: JMenu для содержания других JMenuItem (так что вы можете иметь каскадированное меню), JCheckBoxMenuItem, которое производит отметки, указывающие, было ли выбрано меню, или нет, и JRadioButtonMenuItem, которое содержит радио кнопки.
В качестве более изощренного примере здесь снова используются вкусы мороженого, используемые для создания меню. Этот пример также показывает каскадируемое меню, мнемонические обозначение клавиш, JCheckBoxMenuItem, и способ, которым вы можете динамически менять меню:
//: c13:Menus.java
// Подменю, checkbox-элементы в меню, перестановка меню,
// мнемоники (горячие клавиши) и команды реакции.
// <applet code=Menus width=300
// height=100> </applet>
import javax.swing.*; import java.awt.*; import java.awt.event.*; import com.bruceeckel.swing.*;
public class Menus extends JApplet { String[] flavors = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream", "Mud Pie" }; JTextField t = new JTextField("No flavor", 30); JMenuBar mb1 = new JMenuBar(); JMenu f = new JMenu("File"), m = new JMenu("Flavors"), s = new JMenu("Safety"); // Альтернативный подход:
JCheckBoxMenuItem[] safety = { new JCheckBoxMenuItem("Guard"), new JCheckBoxMenuItem("Hide") }; JMenuItem[] file = { new JMenuItem("Open"), }; // Вторая полоса меню меняется на:
JMenuBar mb2 = new JMenuBar(); JMenu fooBar = new JMenu("fooBar"); JMenuItem[] other = { // Добавление горячих клавиш в меню (мнемоник)
// очень просто, но их могут иметь только JMenuItems
// в своем конструкторе:
new JMenuItem("Foo", KeyEvent.VK_F), new JMenuItem("Bar", KeyEvent.VK_A), // Нет горячей клавиши:
new JMenuItem("Baz"), }; JButton b = new JButton("Swap Menus"); class BL implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuBar m = getJMenuBar(); setJMenuBar(m == mb1 ? mb2 : mb1); validate(); // Обновление фрейма
} } class ML implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuItem target = (JMenuItem)e.getSource(); String actionCommand = target.getActionCommand(); if(actionCommand.equals("Open")) { String s = t.getText(); boolean chosen = false; for(int i = 0; i < flavors.length; i++) if(s.equals(flavors[i])) chosen = true; if(!chosen) t.setText("Choose a flavor first!"); else
t.setText("Opening "+ s +". Mmm, mm!"); } } } class FL implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuItem target = (JMenuItem)e.getSource(); t.setText(target.getText()); } } // Другой способ: вы можете создать другой класс
// для каждого MenuItem. Затем вам
// не нужно следить, кто есть кто:
class FooL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Foo selected"); } } class BarL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Bar selected"); } } class BazL implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Baz selected"); } } class CMIL implements ItemListener { public void itemStateChanged(ItemEvent e) { JCheckBoxMenuItem target = (JCheckBoxMenuItem)e.getSource(); String actionCommand = target.getActionCommand(); if(actionCommand.equals("Guard")) t.setText("Guard the Ice Cream! " + "Guarding is " + target.getState()); else if(actionCommand.equals("Hide")) t.setText("Hide the Ice Cream! " + "Is it cold? " + target.getState()); } } public void init() { ML ml = new ML(); CMIL cmil = new CMIL(); safety[0].setActionCommand("Guard"); safety[0].setMnemonic(KeyEvent.VK_G); safety[0].addItemListener(cmil); safety[1].setActionCommand("Hide"); safety[0].setMnemonic(KeyEvent.VK_H); safety[1].addItemListener(cmil); other[0].addActionListener(new FooL()); other[1].addActionListener(new BarL()); other[2].addActionListener(new BazL()); FL fl = new FL(); for(int i = 0; i < flavors.length; i++) { JMenuItem mi = new JMenuItem(flavors[i]); mi.addActionListener(fl); m.add(mi); // Добавляем разделитель:
if((i+1) % 3 == 0) m.addSeparator(); } for(int i = 0; i < safety.length; i++) s.add(safety[i]); s.setMnemonic(KeyEvent.VK_A); f.add(s); f.setMnemonic(KeyEvent.VK_F); for(int i = 0; i < file.length; i++) { file[i].addActionListener(fl); f.add(file[i]); } mb1.add(f); mb1.add(m); setJMenuBar(mb1); t.setEditable(false); Container cp = getContentPane(); cp.add(t, BorderLayout.CENTER); // Готовим систему к замене меню:
b.addActionListener(new BL()); b.setMnemonic(KeyEvent.VK_S); cp.add(b, BorderLayout.NORTH); for(int i = 0; i < other.length; i++) fooBar.add(other[i]); fooBar.setMnemonic(KeyEvent.VK_B); mb2.add(fooBar); } public static void main(String[] args) { Console.run(new Menus(), 300, 100); } } ///:~
В этой программе я поместил элементы меню в массивы, а затем прошелся по всем массивам, вызывая add( ) для каждого JMenuItem. Это делает добавление или удаление пункта меню менее утомительным.
Эта программа создает не один, а два JMenuBar для демонстрации, что меню может быть заменено во время работы программы. Вы можете посмотреть, как создан JMenuBar из JMenu, и как каждый JMenu создан из JMenuItem, JCheckBoxMenuItem, или даже JMenu (который производит подменю). Когда JMenuBar собран, он может быть установлен в текущей программе с помощью метода setJMenuBar( ). Обратите внимание, что когда нажата кнопка, выполняется проверка какое меню сейчас установлено с помощью вызова метода getJMenuBar( ), а затем помещает другое меню на его место.
Когда проверяете “Open”, обратите внимание, что пробелы и большие буквы - критичны, но Java не сигнализирует об ошибках, если нет совпадений со словом “Open”. Такой род сравнения строк приводи к ошибкам программы.
Пометки и снятие пометки на элементах меню происходит автоматически. Код обработки JCheckBoxMenuItem показывает два разных способа определения, что было помечено: сравнение строк (которое, как упоминалось ранее, является не очень безопасным подходом, хотя вы видите его использование) и сравнение объектов - источников события. Как показано, метод getState( ) может использоваться для проверки состояния. Вы можете также менять состояние JCheckBoxMenuItem с помощью setState( ).
События для меню немного противоречивы и могут запутать: JMenuItem использует ActionListener, а JCheckboxMenuItem использует ItemListener. Объект JMenu может также поддерживать ActionListener, но обычно это бесполезно. В общем случае вы будете присоединять слушатели к каждому JMenuItem, JCheckBoxMenuItem или JRadioButtonMenuItem, но пример показывает ItemListener и ActionListener, присоединенные к различным компонентам меню.
Swing поддерживает мнемоники, или “горячие клавиши”, так что вы можете выбрать все что угодно, унаследованное от AbstractButton (кнопки, элементы меню и т.п.), используя клавиатуру вместо мыши. Это достаточно просто: для JMenuItem вы можете использовать перегруженный конструктор, который принимает второй аргумент, идентифицирующий клавишу. Однако большинство AbstractButton не имеют конструкторов, подобных этому, поэтому есть более общий способ решения проблемы - это использование метода setMnemonic( ). Приведенный выше пример добавляет мнемонику к кнопке и к некоторым из элементов меню; индикатор горячей клавиши в компоненте появляется автоматически.
Вы также можете видеть использование команды setActionCommand( ). Она выглядит немного странной, потому что в каждом случае “команда реакции” точно такая же, как и метка компонента меню. Почему просто не использовать метку вместо этой альтернативной строки? Проблема заключена в интернационализации. Если вы перенесете эту программу на другой язык, вы захотите изменить только метки в меню, и не изменять код (чтобы не вносить новые ошибки). Чтобы выполнить это легче при кодировании, выполняется проверка ассоциированной строки с этим компонентом меню, “команда реакции” может быть неизменной, в то время как метка меню может измениться. Весь код работает с “командой реакции”, так что на него не влияют никакие изменения меток меню. Обратите внимание, что в этой программе не у всех компонент меню проверяется их команда реакции, так что эти элементы не имеют своих установленных команд реакции.
Основная работа происходит в слушателях. BL выполняет обмен JMenuBar. В ML “cмотрят, кто звонил”, при этом подходе берется источник ActionEvent и приводится в типу JMenuItem, затем получается строка команды реакции для передачи ее в каскадную инструкцию if.
Слушатель FL прост, несмотря на то, что он обрабатывает все различные вкусы в меню вкусов. Этот подход полезен, если вы имеете достаточно простую логику, но в общем случае вы будете использовать подход с использованием FooL, BarL и BazL, каждый из которых присоединяется только к одному компоненту меню, так чтобы не нужна было дополнительная логика определения, и вы точно знали, кто вызвал слушателя. Даже с избытком классов, созданных этим способом, внутренний код имеет тенденцию быть меньше, а процесс более понятным.
Вы можете видеть, что код меню быстро становится многозначным и беспорядочным. Это другой случай, где использование построителя GUI является подходящим решением. Хороший инструмент также берет на себя заботы о меню.