JPopupMenu的执行看起来有一些别扭:我们必须调用enableEvents()方法并选择鼠标事件代替利用事件接收器。它可能增加一个鼠标接收器但MouseEvent从isPopupTrigger()处不会返回真值——它不知道将激活一个弹出菜单。另外,当我们尝试接收器方法时,它的行为令人不可思议,这或许是鼠标单击活动引起的。在下面的程序例子里一些事件产生了这种弹出行为:
//: Popup.java // Creating popup menus with Swing package c13.swing; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Popup extends JPanel { JPopupMenu popup = new JPopupMenu(); JTextField t = new JTextField(10); public Popup() { add(t); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e){ t.setText( ((JMenuItem)e.getSource()).getText()); } }; JMenuItem m = new JMenuItem("Hither"); m.addActionListener(al); popup.add(m); m = new JMenuItem("Yon"); m.addActionListener(al); popup.add(m); m = new JMenuItem("Afar"); m.addActionListener(al); popup.add(m); popup.addSeparator(); m = new JMenuItem("Stay Here"); m.addActionListener(al); popup.add(m); PopupListener pl = new PopupListener(); addMouseListener(pl); t.addMouseListener(pl); } class PopupListener extends MouseAdapter { public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if(e.isPopupTrigger()) { popup.show( e.getComponent(), e.getX(), e.getY()); } } } public static void main(String args[]) { Show.inFrame(new Popup(),200,150); } } ///:~
相同的ActionListener被加入每个JMenuItem中,使其能从菜单标签中取出文字,并将文字插入JTextField。
列表框和组合框在Swing中工作就像它们在老的AWT中工作一样,但如果我们需要它,它们同样被增加功能。另外,它也更加的方便易用。例如,JList中有一个显示String数组的构建器(奇怪的是同样的功能在JComboBox中无效!)。下面的例子显示了它们基本的用法。
//: ListCombo.java // List boxes & Combo boxes package c13.swing; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ListCombo extends JPanel { public ListCombo() { setLayout(new GridLayout(2,1)); JList list = new JList(ButtonGroups.ids); add(new JScrollPane(list)); JComboBox combo = new JComboBox(); for(int i = 0; i < 100; i++) combo.addItem(Integer.toString(i)); add(combo); } public static void main(String args[]) { Show.inFrame(new ListCombo(),200,200); } } ///:~
最开始的时候,似乎有点儿古怪的一种情况是JLists居然不能自动提供滚动特性——即使那也许正是我们一直所期望的。增加对滚动的支持变得十分容易,就像上面示范的一样——简单地将JList封装到JScrollPane即可,所有的细节都自动地为我们照料到了。
滑杆用户能用一个滑块的来回移动来输入数据,在很多情况下显得很直观(如声音控制)。进程条从“空”到“满”显示相关数据的状态,因此用户得到了一个状态的透视。我最喜爱的有关这的程序例子简单地将滑动块同进程条挂接起来,所以当我们移动滑动块时,进程条也相应的改变:
//: Progress.java // Using progress bars and sliders package c13.swing; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; public class Progress extends JPanel { JProgressBar pb = new JProgressBar(); JSlider sb = new JSlider(JSlider.HORIZONTAL, 0, 100, 60); public Progress() { setLayout(new GridLayout(2,1)); add(pb); sb.setValue(0); sb.setPaintTicks(true); sb.setMajorTickSpacing(20); sb.setMinorTickSpacing(5); sb.setBorder(new TitledBorder("Slide Me")); pb.setModel(sb.getModel()); // Share model add(sb); } public static void main(String args[]) { Show.inFrame(new Progress(),200,150); } } ///:~
JProgressBar十分简单,但JSlider却有许多选项,例如方法、大或小的记号标签。注意增加一个带标题的边框是多么的容易。
使用一个JTree可以简单地像下面这样表示:
add(new JTree(new Object[] {"this", "that", "other"}));
这个程序显示了一个原始的树状物。树状物的API是非常巨大的,可是——当然是在Swing中的巨大。它表明我们可以做有关树状物的任何事,但更复杂的任务可能需要不少的研究和试验。幸运的是,在库中提供了一个妥协:“默认的”树状物组件,通常那是我们所需要的。因此大多数的时间我们可以利用这些组件,并且只在特殊的情况下我们需要更深入的研究和理解。
下面的例子使用了“默认”的树状物组件在一个程序片中显示一个树状物。当我们按下按钮时,一个新的子树就被增加到当前选中的结点下(如果没有结点被选中,就用根结节):
//: Trees.java // Simple Swing tree example. Trees can be made // vastly more complex than this. package c13.swing; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.tree.*; // Takes an array of Strings and makes the first // element a node and the rest leaves: class Branch { DefaultMutableTreeNode r; public Branch(String[] data) { r = new DefaultMutableTreeNode(data[0]); for(int i = 1; i < data.length; i++) r.add(new DefaultMutableTreeNode(data[i])); } public DefaultMutableTreeNode node() { return r; } } public class Trees extends JPanel { String[][] data = { { "Colors", "Red", "Blue", "Green" }, { "Flavors", "Tart", "Sweet", "Bland" }, { "Length", "Short", "Medium", "Long" }, { "Volume", "High", "Medium", "Low" }, { "Temperature", "High", "Medium", "Low" }, { "Intensity", "High", "Medium", "Low" }, }; static int i = 0; DefaultMutableTreeNode root, child, chosen; JTree tree; DefaultTreeModel model; public Trees() { setLayout(new BorderLayout()); root = new DefaultMutableTreeNode("root"); tree = new JTree(root); // Add it and make it take care of scrolling: add(new JScrollPane(tree), BorderLayout.CENTER); // Capture the tree's model: model =(DefaultTreeModel)tree.getModel(); JButton test = new JButton("Press me"); test.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e){ if(i < data.length) { child = new Branch(data[i++]).node(); // What's the last one you clicked? chosen = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); if(chosen == null) chosen = root; // The model will create the // appropriate event. In response, the // tree will update itself: model.insertNodeInto(child, chosen, 0); // This puts the new node on the // currently chosen node. } } }); // Change the button's colors: test.setBackground(Color.blue); test.setForeground(Color.white); JPanel p = new JPanel(); p.add(test); add(p, BorderLayout.SOUTH); } public static void main(String args[]) { Show.inFrame(new Trees(),200,500); } } ///:~
最重要的类就是分支,它是一个工具,用来获取一个字符串数组并为第一个字符串建立一个DefaultMutableTreeNode作为根,其余在数组中的字符串作为叶。然后node()方法被调用以产生“分支”的根。树状物类包括一个来自被制造的分支的二维字符串数组,以及用来统计数组的一个静态中断i。DefaultMutableTreeNode对象控制这个结节,但在屏幕上表示的是被JTree和它的相关(DefaultTreeModel)模式所控制。注意当JTree被增加到程序片时,它被封装到JScrollPane中——这就是它全部提供的自动滚动。
JTree通过它自己的模型来控制。当我们修改这个模型时,模型产生一个事件,导致JTree对可以看见的树状物完成任何必要的升级。在init()中,模型由调用getModel()方法所捕捉。当按钮被按下时,一个新的分支被创建了。然后,当前选择的组件被找到(如果没有选择就是根)并且模型的insertNodeInto()方法做所有的改变树状物和导致它升级的工作。
大多数的时候,就像上面的例子一样,程序将给我们在树状物中所需要的一切。不过,树状物拥有力量去做我们能够想像到的任何事——在上面的例子中我们到处都可看到“default(默认)”字样,我们可以取代我们自己的类来获取不同的动作。但请注意:几乎所有这些类都有一个具大的接口,因此我们可以花一些时间努力去理解这些错综复杂的树状物。