12.2.2 AWT组件的Swing实现
从图12.1中可以看出,Swing为除Canvas之外的所有AWT组件提供了相应的实现,Swing组件比AWT组件的功能更加强大。相对于AWT组件,Swing组件具有如下4个额外的功能。
- 可以为Swing组件设置提示信息。使用setToolTipText()方法,为组件设置对用户有帮助的提示信息。
- 很多Swing组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在Swing组件中使用图标,Swing为Icon接口提供了一个实现类:ImageIcon,该实现类代表一个图像图标。
- 支持插拔式的外观风格。每个JComponent对象都有一个相应的ComponentUI对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。ComponentUI对象依赖当前使用的PLAF,使用UIManager.SetLookAndFeel()方法可以改变图形界面的外观风格。
- 支持设置边框。Swing组件可以设置一个或多个边框。Swing中提供了各式各样的边框供用户选用,也能建立组合边框或自己设计边框。一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局。
UI代理类
每个Swing组件都有一个对应的UI类,例如JButton组件就有一个对应的ButtonUI类来作为UI代理。每个Swing组件的UI代理的类名总是将该Swing组件类名的J去掉,然后在后面添加UI后缀。UI代理类通常是一个抽象基类,不同的PLAF会有不同的UI代理实现类。Swing类库中包含了几套UI代理,每套UI代理都几乎包含了所有Swing组件的Component实现,每套这样的实现都被称为一种PLAF实现。以JButton为例,其UI代理的继承层次如图12.2所示。
![这里有一张图片]()
如果需要改变程序的外观风格,则可以使用如下代码。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | try{
 UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.windowsLookAndFeel");
 
 SwingUtilities.updateComponentTreeUI(f);
 }
 catch(Exception e){
 e.printstackTrace();
 }
 
 | 
程序Swing窗口
下面程序示范了使用Swing组件来创建窗口应用,该窗口里包含了菜单、右键菜单以及基本AWT组件的Swing实现。
D:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\12\12.2\SwingComponent.java| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 
 | import java.awt.*;import java.awt.event.*;
 import javax.swing.*;
 
 public class SwingComponent {
 JFrame f = new JFrame("测试");
 
 Icon okIcon = new ImageIcon("ico/ok.png");
 JButton ok = new JButton("确认", okIcon);
 
 JRadioButton male = new JRadioButton("男", true);
 
 JRadioButton female = new JRadioButton("女", false);
 
 ButtonGroup bg = new ButtonGroup();
 
 JCheckBox married = new JCheckBox("是否已婚?", false);
 String[] colors = new String[] { "红色", "绿色", "蓝色" };
 
 JComboBox<String> colorChooser = new JComboBox<>(colors);
 
 JList<String> colorList = new JList<>(colors);
 
 JTextArea ta = new JTextArea(8, 20);
 
 JTextField name = new JTextField(40);
 JMenuBar mb = new JMenuBar();
 JMenu file = new JMenu("文件");
 JMenu edit = new JMenu("编辑");
 
 Icon newIcon = new ImageIcon("ico/new.png");
 JMenuItem newItem = new JMenuItem("新建", newIcon);
 
 Icon saveIcon = new ImageIcon("ico/save.png");
 JMenuItem saveItem = new JMenuItem("保存", saveIcon);
 
 Icon exitIcon = new ImageIcon("ico/exit.png");
 JMenuItem exitItem = new JMenuItem("退出", exitIcon);
 JCheckBoxMenuItem autoWrap = new JCheckBoxMenuItem("自动换行");
 
 JMenuItem copyItem = new JMenuItem("复制", new ImageIcon("ico/copy.png"));
 
 JMenuItem pasteItem = new JMenuItem("粘贴", new ImageIcon("ico/paste.png"));
 JMenu format = new JMenu("格式");
 JMenuItem commentItem = new JMenuItem("注释");
 JMenuItem cancelItem = new JMenuItem("取消注释");
 
 JPopupMenu pop = new JPopupMenu();
 
 ButtonGroup flavorGroup = new ButtonGroup();
 
 JRadioButtonMenuItem metalItem = new JRadioButtonMenuItem("Metal风格", true);
 JRadioButtonMenuItem nimbusItem = new JRadioButtonMenuItem("Nimbus风格");
 JRadioButtonMenuItem windowsItem = new JRadioButtonMenuItem("Windows风格");
 JRadioButtonMenuItem classicItem = new JRadioButtonMenuItem("Windows经典风格");
 JRadioButtonMenuItem motifItem = new JRadioButtonMenuItem("Motif风格");
 
 
 public void init() {
 
 JPanel bottom = new JPanel();
 bottom.add(name);
 bottom.add(ok);
 f.add(bottom, BorderLayout.SOUTH);
 
 JPanel checkPanel = new JPanel();
 checkPanel.add(colorChooser);
 bg.add(male);
 bg.add(female);
 checkPanel.add(male);
 checkPanel.add(female);
 checkPanel.add(married);
 
 Box topLeft = Box.createVerticalBox();
 
 JScrollPane taJsp = new JScrollPane(ta);
 topLeft.add(taJsp);
 topLeft.add(checkPanel);
 
 Box top = Box.createHorizontalBox();
 top.add(topLeft);
 top.add(colorList);
 
 f.add(top);
 
 
 newItem.setAccelerator(KeyStroke.getKeyStroke('N', InputEvent.CTRL_MASK));
 newItem.addActionListener(e -> ta.append("用户单击了“新建”菜单\n"));
 
 file.add(newItem);
 file.add(saveItem);
 file.add(exitItem);
 
 edit.add(autoWrap);
 
 edit.addSeparator();
 edit.add(copyItem);
 edit.add(pasteItem);
 
 commentItem.setToolTipText("将程序代码注释起来!");
 
 format.add(commentItem);
 format.add(cancelItem);
 
 edit.add(new JMenuItem("-"));
 
 edit.add(format);
 
 mb.add(file);
 mb.add(edit);
 
 f.setJMenuBar(mb);
 
 flavorGroup.add(metalItem);
 flavorGroup.add(nimbusItem);
 flavorGroup.add(windowsItem);
 flavorGroup.add(classicItem);
 flavorGroup.add(motifItem);
 pop.add(metalItem);
 pop.add(nimbusItem);
 pop.add(windowsItem);
 pop.add(classicItem);
 pop.add(motifItem);
 
 ActionListener flavorListener = e -> {
 try {
 switch (e.getActionCommand()) {
 case "Metal风格":
 changeFlavor(1);
 break;
 case "Nimbus风格":
 changeFlavor(2);
 break;
 case "Windows风格":
 changeFlavor(3);
 break;
 case "Windows经典风格":
 changeFlavor(4);
 break;
 case "Motif风格":
 changeFlavor(5);
 break;
 }
 } catch (Exception ee) {
 ee.printStackTrace();
 }
 };
 
 metalItem.addActionListener(flavorListener);
 nimbusItem.addActionListener(flavorListener);
 windowsItem.addActionListener(flavorListener);
 classicItem.addActionListener(flavorListener);
 motifItem.addActionListener(flavorListener);
 
 ta.setComponentPopupMenu(pop);
 
 f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 f.pack();
 f.setVisible(true);
 }
 
 
 private void changeFlavor(int flavor) throws Exception {
 switch (flavor) {
 
 case 1:
 UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
 break;
 
 case 2:
 UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
 break;
 
 case 3:
 UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
 break;
 
 case 4:
 UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
 break;
 
 case 5:
 UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
 break;
 }
 
 SwingUtilities.updateComponentTreeUI(f.getContentPane());
 
 SwingUtilities.updateComponentTreeUI(mb);
 
 SwingUtilities.updateComponentTreeUI(pop);
 }
 
 public static void main(String[] args) {
 
 
 new SwingComponent().init();
 }
 }
 
 | 
 
上面程序在创建按钮、菜单项时传入了一个ImageIcon对象,通过这种方式就可以创建带图标的按钮、菜单项。程序的init方法中的粗体字代码用于为comment菜单项添加提示信息。运行上面程序,并通过右键菜单选择“NimbusLAF”,可以看到如图12.3所示的窗口。
图12.3Nimbus格Swing
从图12.3中可以看出,Swing菜单不允许使用add(new JMenuItem("-")的方式来添加菜单分隔符,只能使用addSeparator()方法来添加菜单分隔符。
提示:Swing专门为菜单项、工具按钮之间的分隔符提供了一个JSeparator类,通常使用JMenu或者JPopupMenu的addSeparator()方法来创建并添加JSeparator对象,而不是直接使用JSeparator。实际上,JSeparator可以用在任何需要使用分隔符的地方。
上面程序为newItem菜单项增加了快捷键,为Swing菜单项指定快捷键与为AWT菜单项指定快捷键的方式有所不同——创建AWT菜单对象时可以直接传入KeyShortcut对象为其指定快捷键;但为Swing菜单项指定快捷键时必须通过setAccelerator(KeyStroke ks)方法来设置(如 1号代码 处程序所示),其中KeyStroke代表一次击键动作,可以直接通过按键对应字母来指定该击键动作。
提示:为菜单项指定快捷键时应该使用大写字母来代表按键,例如KeyStroke.getKeyStroke('N', InputEvent.CTRL_MASK)代表 “Ctrl+N”,但KeyStroke.getKeyStroke('n', InputEvent.CTRL MASK)则不代表“Ctrl+N”。
除此之外,上面程序中的大段粗体字代码所定义的changeFlavor()方法用于改变程序外观风格,当用户单击多行文本域里的右键菜单时将会触发该方法,该方法设置Swing组件的外观风格后,再次调用SwingUtilities类的updateComponentTreeUI()方法来更新指定容器,以及该容器内所有组件的UI。注意此处更新的是JFrame对象getContentPane()方法的返回值,而不是直接更新JFrame对象本身(如 2号代码 处程序所示)。这是因为如果直接更新JFrame本身,将会导致JFrame也被更新,JFrame是一个特殊的容器,JFrame依然部分依赖于本地平台的图形组件。尤其是当取消 3号代码 处的注释后,JFrame将会使用Java风格的标题栏、边框,如果强制JFrame更新成Windows Motif格,则该窗口去标题栏和边框。如果通过右键菜单选择程序使用Motif风格,将看到如图12.4所示的窗口。
图12.4使用Java风格窗口标题、边框、Motif显示风格的窗口
提示:JFrame提供了一个getContentPane()方法,这个方法用于返回该JFrame的顶级容器(即JRootPane对象),这个顶级容器会包含JFrame所显示的所有非菜单组件。可以这样理解:所有看似放在JFrame中的Swing组件,除菜单之外,其实都是放在JFrame对应的顶级容器中的,而JFrame容器里提供了getContentPane()方法返回的顶级容器。在Java 5以前,Java甚至不允许直接向Jframe中添加组件,必须先调用Jframe的getContentPane()方法获得该窗口的顶级容器,然后将所有组件添加到该顶级容器中。从Java 5以后,Java改写了JFrame的add()和setLayout()等方法,当程序调用JFrame的add()和setLayout()等方法时,实际上是对JFrame的顶级容器进行操作。
从程序中 4号代码 处可以看出,为Swing组件添加右键菜单无须像AWT中那样烦琐,只需要简单地调用setComponentPopupMenu()方法来设置右键菜单即可,无须编写事件监听器。由此可见,使用Swing组件编写图形界面程序更加简单。
除此之外,如果程序希望用户单击窗口右上角的“×”按钮时,程序退出,也无须使用事件机制,只要调用setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)方法即可, Swing提供的这种方式也是为了简化界面编程。
JScrollPane组件是一个特殊的组件,它不同于JFrame、JPanel等普通容器,它甚至不能指定自己的布局管理器,它主要用于为其他的Swing组件提供滚动条支持,JScrollPane通常由普通的Swing组件,可选的垂直、水平滚动条以及可选的行、列标题组成。
简而言之,如果希望让JTextArea、JTable等组件能有滚动条支持,只要将该组件放入JScrolIPane中,再将该JScrollPane容器添加到窗口中即可。关于JScrolIPane的详细说明,读者可以参考JScrollPane的API文档。
学生提问:为什么单击Swing多行文本域时不是弹出像AWT多行文本域中的右键菜单?
答:这是由Swing组件和AWT组件实现机制不同决定的。前面已经指出,AWT的多行文本域实际上依赖于本地平台的多行文本域。简单地说,当我们在程序中放置一个AWT多行文本域,且该程序在Windows平台上运行时,该文本域组件将和记事本工具编辑区具有相同的行为方式,因为该文本域组件和记事本工具编辑区的底层实现是一样的。但Swing的多行文本域组件则是纯Java的,它无须任何本地平台GUI的支持,它在任何平台上都具有相同的行为方式,所以Swing多行文本域组件默认是没有右键菜单的,必须由程序员显式为它分配右键菜单。而且,Swing提供的JTextArea组件默认没有滚动条(AWT的TextArea是否有滚动条则取决于底层平台的实现),为了让该多行文本域具有滚动条,可以将该多行文本域放到JScrollPane容器中。
提示:JScrollPane对于JTable组件尤其重要,通常需要把JTable放在JScrollPane容器中才可以显示出JTable组件的标题栏。