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所示。
如果需要改变程序的外观风格,则可以使用如下代码。
1 2 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.java1 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 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组件的标题栏。