12.2.2 AWT组件的Swing实现

12.2.2 AWT组件的Swing实现

从图12.1中可以看出,Swing为除Canvas之外的所有AWT组件提供了相应的实现,Swing组件比AWT组件的功能更加强大。相对于AWT组件,Swing组件具有如下4个额外的功能。

  1. 可以为Swing组件设置提示信息。使用setToolTipText()方法,为组件设置对用户有帮助的提示信息。
  2. 很多Swing组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在Swing组件中使用图标,SwingIcon接口提供了一个实现类:ImageIcon,该实现类代表一个图像图标。
  3. 支持插拔式的外观风格。每个JComponent对象都有一个相应的ComponentUI对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。ComponentUI对象依赖当前使用的PLAF,使用UIManager.SetLookAndFeel()方法可以改变图形界面的外观风格。
  4. 支持设置边框。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所示。

这里有一张图片

如果需要改变程序的外观风格,则可以使用如下代码。

1
2
3
4
5
6
7
8
9
try{
//设置使用Windows风格
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.windowsLookAndFeel");
// 通过更新f容器以及f容器里所有组件的UI
SwingUtilities.updateComponentTreeUI(f);
}
catch(Exception e){
e.printstackTrace();
}

程序Swing窗口

下面程序示范了使用Swing组件来创建窗口应用,该窗口里包含了菜单、右键菜单以及基本AWT组件的Swing实现。

D:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\12\12.2\SwingComponent.java
1
2
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,用于将上面两个JRadioButton组合在一起
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);
// 定义一个8行、20列的多行文本域
JTextArea ta = new JTextArea(8, 20);
// 定义一个40列的单行文本域
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
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风格");

// -----------------用于执行界面初始化的init方法---------------------
public void init() {
// 创建一个装载了文本框、按钮的JPanel
JPanel bottom = new JPanel();
bottom.add(name);
bottom.add(ok);
f.add(bottom, BorderLayout.SOUTH);
// 创建一个装载了下拉选择框、三个JCheckBox的JPanel
JPanel checkPanel = new JPanel();
checkPanel.add(colorChooser);
bg.add(male);
bg.add(female);
checkPanel.add(male);
checkPanel.add(female);
checkPanel.add(married);
// 创建一个垂直排列组件的Box,盛装多行文本域JPanel
Box topLeft = Box.createVerticalBox();
// 使用JScrollPane作为普通组件的JViewPort
JScrollPane taJsp = new JScrollPane(ta); // ⑤
topLeft.add(taJsp);
topLeft.add(checkPanel);
// 创建一个水平排列组件的Box,盛装topLeft、colorList
Box top = Box.createHorizontalBox();
top.add(topLeft);
top.add(colorList);
// 将top Box容器添加到窗口的中间
f.add(top);
// -----------下面开始组合菜单、并为菜单添加监听器----------
// 为newItem设置快捷键,设置快捷键时要使用大写字母
newItem.setAccelerator(KeyStroke.getKeyStroke('N', InputEvent.CTRL_MASK)); // 1号代码
newItem.addActionListener(e -> ta.append("用户单击了“新建”菜单\n"));
// 为file菜单添加菜单项
file.add(newItem);
file.add(saveItem);
file.add(exitItem);
// 为edit菜单添加菜单项
edit.add(autoWrap);
// 使用addSeparator方法来添加菜单分隔线
edit.addSeparator();
edit.add(copyItem);
edit.add(pasteItem);
// 为commentItem组件添加提示信息
commentItem.setToolTipText("将程序代码注释起来!");
// 为format菜单添加菜单项
format.add(commentItem);
format.add(cancelItem);
// 使用添加new JMenuItem("-")的方式不能添加菜单分隔符
edit.add(new JMenuItem("-"));
// 将format菜单组合到edit菜单中,从而形成二级菜单
edit.add(format);
// 将file、edit菜单添加到mb菜单条中
mb.add(file);
mb.add(edit);
// 为f窗口设置菜单条
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);
// 为5个风格菜单创建事件监听器
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); // 4号代码
// 设置关闭窗口时,退出程序
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}

// 定义一个方法,用于改变界面风格
private void changeFlavor(int flavor) throws Exception {
switch (flavor) {
// 设置Metal风格
case 1:
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
break;
// 设置Nimbus风格
case 2:
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
break;
// 设置Windows风格
case 3:
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
break;
// 设置Windows经典风格
case 4:
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel");
break;
// 设置Motif风格
case 5:
UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel");
break;
}
// 更新f窗口内顶级容器以及内部所有组件的UI
SwingUtilities.updateComponentTreeUI(f.getContentPane()); // 2号代码
// 更新mb菜单条以及内部所有组件的UI
SwingUtilities.updateComponentTreeUI(mb);
// 更新pop右键菜单以及内部所有组件的UI
SwingUtilities.updateComponentTreeUI(pop);
}

public static void main(String[] args) {
// 设置Swing窗口使用Java风格
// JFrame.setDefaultLookAndFeelDecorated(true); //3号代码
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组件的标题栏。