12.2.7 使用JOptionPane

通过JOptionPane可以非常方便地创建一些简单的对话框,Swing已经为这些对话框添加了相应的组件,无须程序员手动添加组件。JOptionPane提供了如下4个方法来创建对话框。

方法 描述
showMessageDialog/showInternalMessageDialog 消息对话框,告知用户某事已发生,用户只能单击“确定”按钮,类似于JavaScriptalert函数。
showConfirmDialog/showInternalConfirmDialog 确认对话框,向用户确认某个问题,用户可以选择yesnocancel等选项。类似于JavaScriptcomfirm函数。该方法返回用户单击了哪个按钮。
showInputDialog/showInternalInputDialog 输入对话框,提示要求输入某些信息,类似于JavaScriptprompt函数。该方法返回用户输入的字符串。
showOptionDialog/showInternalOptionDialog 自定义选项对话框,允许使用自定义选项,可以取代showConfirmDialog所产生的对话框,只是用起来更复杂。

JOptionPane产生的所有对话框都是模式的,在用户完成与对话框的交互之前,showXxxDialog方法都将一直阻塞当前线程

JOptionPane对话框 区域划分

JOptionPane所产生的对话框总是具有如图12.10所示的布局:

这里有一张图片

上面这些方法都提供了相应的showInternalXxxDialog版本,这种方法以InternalFrame的方式打开对话框。

下面就图12.10中所示的4个区域分别进行介绍

(1)输入区

如果创建的对话框无须接收用户输入,则输入区不存在。输入区组件可以是普通文本框组件,也可以是下拉列表框组件。

如果调用上面的showInternalXxxDialog()方法时指定了一个数组类型的selectionValues参数,则输入区包含一个下拉列表框组件。

(2)图标区

左上角的图标会随创建的对话框所包含消息类型的不同而不同,JOptionPane可以提供如下5种消息类型。

messageType参数 描述
JOptionPane.ERROR_MESSAGE 错误消息,其图标是一个红色的X图标,如图12.10所示
JOptionPane.INFORMATION_MESSAGE 普通消息,其默认图标是蓝色的感叹号。
JOptionPane.WARNING_MESSAGE 警告消息,其默认图标是黄色感叹号。
JOptionPane.QUESTION_MESSAGE 问题消息,其默认图标是绿色问号。
JOptionPane.PLAIN_MESSAGE 普通消息,没有默认图标

实际上,JOptionPane的所有showXxxDialog()方法都可以提供一个可选的icon参数,用于指定该对话框的图标。

调用showXxxDialog方法时还可以指定一个可选的title参数,该参数指定所创建对话框的标题。

(3)消息区

不管是哪种对话框,其消息区总是存在的,消息区的内容通过message参数来指定,根据message参数的类型不同,消息区显示的内容也是不同的。该message参数可以是如下几种类型

  • String类型:将该字符串对象包装成JLabel对象,然后显示在对话框中。
  • Icon:将该Icon被包装成JLabel后作为对话框的消息
  • Component:将该Component在对话框的消息区中显示出来。
  • Object[]:对象数组被解释为在纵向排列的一系列message对象,每个message对象根据其实际类型又可以是字符串、图标、组件、对象数组等。
  • 其他类型:系统调用该对象的toString方法返回一个字符串,并将该字符串对象包装成JLabel对象,然后显示在对话框中。

大部分时候对话框的消息区都是普通字符串,但使用Component作为消息区组件则更加灵活,因为该Component参数几乎可以是任何对象,从而可以让对话框的消息区包含任何内容。

(4)按钮区

对话框底部的按钮区也是一定存在的,但所包含的按钮则会随对话框的类型、选项类型而改变。

输入框showInputDialog和消息框showMessageDialog的按钮

对于调用showInputDialog()showMessageDialog()方法得到的对话框,底部总是包含“确定”和“取消”两个标准按钮。

确认对话框showConfirmDialog的按钮

对于showConfirmDialog()所打开的确认对话框,则可以指定一个整数类型的optionType参数,该参数可以取如下几个值。

optionType参数 描述
JOptionPane.DEFAULT_OPTION 按钮区只包含一个“确定”按钮。
JOptionPane.YES_NO_OPTION 按钮区包含“是”、“否”两个按钮。
JOptionPane.YES_NO_CANCEL_OPTION 按钮区包含“是”、“否”、“取消”三个按钮。
JOptionPane.OK_CANCEL_OPTION 按钮区包含“确定”、“取消”两个按钮。

选项对话框showOptionDialog的按钮

方法 描述
static int showOptionDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue) Brings up a dialog with a specified icon, where the initial choice is determined by the initialValue parameter and the number of choices is determined by the optionType parameter.

如果使用showOptionDialog方法来创建选项对话框,则可以通过指定一个Object[]类型的options参数来设置按钮区能使用的选项按钮。与前面的message参数类似的是,options数组的数组元素可以是如下几种类型。

options数组可用类型 描述
String 使用该字符串来创建一个JButton,并将其显示在按钮区。
Icon 使用该Icon来创建一个JButton,并将其显示在按钮区。
Component 直接将该组件显示在按钮区。
其他类型 系统调用该对象的toString方法返回一个字符串,并使用该字符串来创建一个JButton,并将其显示在按钮区。

对话框的返回值

当用户与对话框交互结束后,不同类型对话框的返回值如下。

创建方法 返回值
showMessageDialog 无返回值。
showInputDialog 返回用户输入或选择的字符串。
showConfirmDialog 返回一个整数代表用户选择的选项。
showOptionDialog 返回一个整数代表用户选择的选项,如果用户选择第一项,则返回0;如果选择第二项,则返回1;……依此类推。

showConfirmDialog的返回值

showConfirmDialog所产生的对话框,有如下几个返回值

showConfirmDialog返回值 描述
JOptionPane.YES_OPTION 用户单击了“是”按钮后返回。
JOptionPane.NO_OPTION 用户单击了“否”按钮后返回。
JOptionPane.CANCEL_OPTION 用户单击了“取消”按钮后返回。
JOptionPane.OK_OPTION 用户单击了“确定”按钮后返回
JOptionPane.CLOSED_OPTION 用户单击了对话框右上角的“x”按钮后返回。

对于showOptionDialog方法所产生的对话框,也可能返回一个CLOSED_OPTION值,当用户单击了对话框右上角的“x”按钮后将返回该值
图12.11已经非常清楚地显示了JOptionPane所支持的4种对话框,以及所有对话框的通用选项、每个对话框的特定选项。

程序 对话框

下面程序允许使用JOptionPane来弹出各种对话框

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
import java.util.Date;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class JOptionPaneTest {
JFrame jf = new JFrame("测试JOptionPane");
// 分别定义6个面板用于定义对话框的几种选项
private ButtonPanel messagePanel;
private ButtonPanel messageTypePanel;
private ButtonPanel msgPanel;
private ButtonPanel confirmPanel;
private ButtonPanel optionsPanel;
private ButtonPanel inputPanel;
private String messageString = "消息区内容";
private Icon messageIcon = new ImageIcon("ico/heart.png");
private Object messageObject = new Date();
private Component messageComponent = new JButton("组件消息");
private JButton msgBn = new JButton("消息对话框");
private JButton confrimBn = new JButton("确认对话框");
private JButton inputBn = new JButton("输入对话框");
private JButton optionBn = new JButton("选项对话框");

public void init() {
JPanel top = new JPanel();
top.setBorder(new TitledBorder(new EtchedBorder(), "对话框的通用选项", TitledBorder.CENTER, TitledBorder.TOP));
top.setLayout(new GridLayout(1, 2));
// 消息类型Panel,该Panel中的选项决定对话框的图标
messageTypePanel = new ButtonPanel("选择消息的类型", new String[] { "ERROR_MESSAGE", "INFORMATION_MESSAGE",
"WARNING_MESSAGE", "QUESTION_MESSAGE", "PLAIN_MESSAGE" });
// 消息内容类型的Panel,该Panel中的选项决定对话框的消息区的内容
messagePanel = new ButtonPanel("选择消息内容的类型", new String[] { "字符串消息", "图标消息", "组件消息", "普通对象消息", "Object[]消息" });
top.add(messageTypePanel);
top.add(messagePanel);
JPanel bottom = new JPanel();
bottom.setBorder(new TitledBorder(new EtchedBorder(), "弹出不同的对话框", TitledBorder.CENTER, TitledBorder.TOP));
bottom.setLayout(new GridLayout(1, 4));
// 创建用于弹出消息对话框的Panel
msgPanel = new ButtonPanel("消息对话框", null);
msgBn.addActionListener(new ShowAction());
msgPanel.add(msgBn);
// 创建用于弹出确认对话框的Panel
confirmPanel = new ButtonPanel("确认对话框",
new String[] { "DEFAULT_OPTION", "YES_NO_OPTION", "YES_NO_CANCEL_OPTION", "OK_CANCEL_OPTION" });
confrimBn.addActionListener(new ShowAction());
confirmPanel.add(confrimBn);
// 创建用于弹出输入对话框的Panel
inputPanel = new ButtonPanel("输入对话框", new String[] { "单行文本框", "下拉列表选择框" });
inputBn.addActionListener(new ShowAction());
inputPanel.add(inputBn);
// 创建用于弹出选项对话框的Panel
optionsPanel = new ButtonPanel("选项对话框", new String[] { "字符串选项", "图标选项", "对象选项" });
optionBn.addActionListener(new ShowAction());
optionsPanel.add(optionBn);
bottom.add(msgPanel);
bottom.add(confirmPanel);
bottom.add(inputPanel);
bottom.add(optionsPanel);
Box box = new Box(BoxLayout.Y_AXIS);
box.add(top);
box.add(bottom);
jf.add(box);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.pack();
jf.setVisible(true);
}

// 根据用户选择返回选项类型
private int getOptionType() {
switch (confirmPanel.getSelection()) {
case "DEFAULT_OPTION":
return JOptionPane.DEFAULT_OPTION;
case "YES_NO_OPTION":
return JOptionPane.YES_NO_OPTION;
case "YES_NO_CANCEL_OPTION":
return JOptionPane.YES_NO_CANCEL_OPTION;
default:
return JOptionPane.OK_CANCEL_OPTION;
}
}

// 根据用户选择返回消息
private Object getMessage() {
switch (messagePanel.getSelection()) {
case "字符串消息":
return messageString;
case "图标消息":
return messageIcon;
case "组件消息":
return messageComponent;
case "普通对象消息":
return messageObject;
default:
return new Object[] { messageString, messageIcon, messageObject, messageComponent };
}
}

// 根据用户选择返回消息类型(决定图标区的图标)
private int getDialogType() {
switch (messageTypePanel.getSelection()) {
case "ERROR_MESSAGE":
return JOptionPane.ERROR_MESSAGE;
case "INFORMATION_MESSAGE":
return JOptionPane.INFORMATION_MESSAGE;
case "WARNING_MESSAGE":
return JOptionPane.WARNING_MESSAGE;
case "QUESTION_MESSAGE":
return JOptionPane.QUESTION_MESSAGE;
default:
return JOptionPane.PLAIN_MESSAGE;
}
}

private Object[] getOptions() {
switch (optionsPanel.getSelection()) {
case "字符串选项":
return new String[] { "a", "b", "c", "d" };
case "图标选项":
return new Icon[] { new ImageIcon("ico/1.gif"), new ImageIcon("ico/2.gif"), new ImageIcon("ico/3.gif"),
new ImageIcon("ico/4.gif") };
default:
return new Object[] { new Date(), new Date(), new Date() };
}
}

// 为各按钮定义事件监听器
private class ShowAction implements ActionListener {
public void actionPerformed(ActionEvent event) {
switch (event.getActionCommand()) {
case "确认对话框":
JOptionPane.showConfirmDialog(jf, getMessage(), "确认对话框", getOptionType(), getDialogType());
break;
case "输入对话框":
if (inputPanel.getSelection().equals("单行文本框")) {
JOptionPane.showInputDialog(jf, getMessage(), "输入对话框", getDialogType());
} else {
JOptionPane.showInputDialog(jf, getMessage(), "输入对话框", getDialogType(), null,
new String[] { "轻量级Java EE企业应用实战", "疯狂Java讲义" }, "疯狂Java讲义");
}
break;
case "消息对话框":
JOptionPane.showMessageDialog(jf, getMessage(), "消息对话框", getDialogType());
break;
case "选项对话框":
JOptionPane.showOptionDialog(jf, getMessage(), "选项对话框", getOptionType(), getDialogType(), null,
getOptions(), "a");
break;
}
}
}

public static void main(String[] args) {
new JOptionPaneTest().init();
}
}

// 定义一个JPanel类扩展类,该类的对象包含多个纵向排列的
// JRadioButton控件,且Panel扩展类可以指定一个字符串作为TitledBorder
class ButtonPanel extends JPanel {
private ButtonGroup group;

public ButtonPanel(String title, String[] options) {
setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), title));
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
group = new ButtonGroup();
for (int i = 0; options != null && i < options.length; i++) {
JRadioButton b = new JRadioButton(options[i]);
b.setActionCommand(options[i]);
add(b);
group.add(b);
b.setSelected(i == 0);
}
}

// 定义一个方法,用于返回用户选择的选项
public String getSelection() {
return group.getSelection().getActionCommand();
}
}

运行上面程序,会看到如图12.11所示的窗口。

这里有一张图片

图12.11已经非常清楚地显示了JOptionPane所支持的4种对话框,以及所有对话框的通用选项、每个对话框的特定选项。

读者可以通过运行上面程序来查看JOptionPane所创建的各种对话框。

12.2.6 使用JFileChoose和Java7增强的JColorChooser

JColorChooser

JColorChooser用于创建颜色选择器对话框,该类的用法非常简单,该类主要提供了如下两个静态方法

方法 描述
JColorChooser() Creates a color chooser pane with an initial color of white.
JColorChooser(Color initialColor) Creates a color chooser pane with the specified initial color.
JColorChooser(ColorSelectionModel model) Creates a color chooser pane with the specified ColorSelectionModel.

showDialog( Component component, String title, Color initialColor)

方法 描述
static Color showDialog(Component component, String title, Color initialColor) 显示一个模式的颜色选择器对话框,该方法返回用户所选颜色。其中component指定该对话框的parent组件,而title指定该对话框的标题,大部分时候都使用该方法来让用户选择颜色。
static Color showDialog(Component component, String title, Color initialColor, boolean colorTransparencySelectionEnabled) Shows a modal color-chooser dialog and blocks until the dialog is hidden.
static JDialog createDialog(Component c, String title, boolean modal, JColorChooser chooserPane, ActionListener okListener, ActionListener cancelListener) 该方法返回一个对话框,该对话框内包含指定的颜色选择器,该方法可以指定该对话框是模式的还是非模式的(通过modal参数指定),还可以指定该对话框内“确定”按钮的事件监听器(通过okListener参数指定)和“取消”按钮的事件监听器(通过cancelListener参数指定)。

Java7JColorChooser增加了一个HSV标签页,允许用户通过HSV模式来选择颜色。

程序 JColorchooser颜色选择器

下面程序改写了前一章的HandDraw程序,改为使用JPanel作为绘图组件,而且使用JColorchooser来弹出颜色选择器对话框。

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
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;

public class HandDraw {
// 画图区的宽度
private final int AREA_WIDTH = 500;
// 画图区的高度
private final int AREA_HEIGHT = 400;
// 下面的preX、preY保存了上一次鼠标拖动事件的鼠标坐标
private int preX = -1;
private int preY = -1;
// 定义一个右键菜单用于设置画笔颜色
JPopupMenu pop = new JPopupMenu();
JMenuItem chooseColor = new JMenuItem("选择颜色");
// 定义一个BufferedImage对象
BufferedImage image = new BufferedImage(AREA_WIDTH, AREA_HEIGHT, BufferedImage.TYPE_INT_RGB);
// 获取image对象的Graphics
Graphics g = image.getGraphics();
private JFrame f = new JFrame("简单手绘程序");
private DrawCanvas drawArea = new DrawCanvas();
// 用于保存画笔颜色
private Color foreColor = new Color(255, 0, 0);

public void init() {
chooseColor.addActionListener(ae -> {
// 下面代码直接弹出一个模式的颜色选择对话框,并返回用户选择的颜色
// 1号代码
// foreColor = JColorChooser.showDialog(f, "选择画笔颜色", foreColor);
// 下面代码则弹出一个非模式的颜色选择对话框,
// 并可以分别为“确定”按钮、“取消”按钮指定事件监听器
// 2号代码 开始
final JColorChooser colorPane = new JColorChooser(foreColor);
JDialog jd = JColorChooser.createDialog(f, "选择画笔颜色", false, colorPane,
e -> foreColor = colorPane.getColor(), null);
// 2号代码 结束
jd.setVisible(true);
});
// 将菜单项组合成右键菜单
pop.add(chooseColor);
// 将右键菜单添加到drawArea对象中
drawArea.setComponentPopupMenu(pop);
// 将image对象的背景色填充成白色
g.fillRect(0, 0, AREA_WIDTH, AREA_HEIGHT);
drawArea.setPreferredSize(new Dimension(AREA_WIDTH, AREA_HEIGHT));
// 监听鼠标移动动作
drawArea.addMouseMotionListener(new MouseMotionAdapter() {
// 实现按下鼠标键并拖动的事件处理器
public void mouseDragged(MouseEvent e) {
// 如果preX和preY大于0
if (preX > 0 && preY > 0) {
// 设置当前颜色
g.setColor(foreColor);
// 绘制从上一次鼠标拖动事件点到本次鼠标拖动事件点的线段
g.drawLine(preX, preY, e.getX(), e.getY());
}
// 将当前鼠标事件点的X、Y坐标保存起来
preX = e.getX();
preY = e.getY();
// 重绘drawArea对象
drawArea.repaint();
}
});
// 监听鼠标事件
drawArea.addMouseListener(new MouseAdapter() {
// 实现鼠标松开的事件处理器
public void mouseReleased(MouseEvent e) {
// 松开鼠标键时,把上一次鼠标拖动事件的X、Y坐标设为-1。
preX = -1;
preY = -1;
}
});
f.add(drawArea);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setVisible(true);
}

public static void main(String[] args) {
new HandDraw().init();
}

// 让画图区域继承JPanel类
class DrawCanvas extends JPanel {
private static final long serialVersionUID = -3097175472553624890L;

// 重写JPanel的paint方法,实现绘画
public void paint(Graphics g) {
// 将image绘制到该组件上
g.drawImage(image, 0, 0, null);
}
}
}

上面程序分别使用了两种方式来弹出颜色选择对话框,其中1号代码可弹出一个模式的颜色选择对话框,并直接返回用户选择的颜色。这种方式简单明了,编程简单。
如果程序有更多额外的需要,则使用程序2号代码,弹出一个非模式的颜色选择对话框(允许程序设定),并为“确定”按钮指定了事件监听器,而“取消”按钮的事件监听器为null(也可以为该按钮指定事件监听器)。
Swing的颜色选择对话框如图12.8所示:

这里有一张图片

从图12.8中可以看出,Swing的颜色选择对话框提供了5种方式来选择颜色,图中显示了HSV方式、CMYK方式的颜色选择器,除此之外,该颜色选择器还可以使用RGBHSL方式来选择颜色。

Java6时,ColorChooser只提供了三种颜色选择方式,图12.8中看到的HSVCMYK两种颜色选择方式都是新增的。

JFileChooser

JFileChooser的功能与AWT中的FileDialog基本相似,也是用于生成“打开文件”、“保存文件”对话框;与FileDialog不同的是,JFileChooser无须依赖于本地平台的GUI,它由100%纯Java实现,在所有平台上具有完全相同的行为,并可以在所有平台上具有相同的外观风格。

构造器

为了调用JFileChooser来打开一个文件对话框,必须先创建该对话框的实例,JFileChooser提供了多个构造器来创建JFileChooser对象,它的构造器总共包含两个参数。

方法 描述
JFileChooser() Constructs a JFileChooser pointing to the user’s default directory.
JFileChooser(File currentDirectory) Constructs a JFileChooser using the given File as the path.
JFileChooser(File currentDirectory, FileSystemView fsv) Constructs a JFileChooser using the given current directory and FileSystemView.
JFileChooser(String currentDirectoryPath) Constructs a JFileChooser using the given path.
JFileChooser(String currentDirectoryPath, FileSystemView fsv) Constructs a JFileChooser using the given current directory path and FileSystemView.
JFileChooser(FileSystemView fsv) Constructs a JFileChooser using the given FileSystemView.

currentDirectory:指定所创建文件对话框的当前路径
currentDirectoryPath:指定所创建文件对话框的当前路径
FileSystemView:用于指定基于该文件系统外观来创建文件对话框,如果没有指定该参数,则默认以当前文件系统外观创建文件对话框。

显示隐藏文件对话框

JFileChooser并不是JDialog的子类,所以不能使用setVisible(true)方法来显示该文件对话框,而是调用showXxxDialog()方法来显示文件对话框

建立文件对话框 步骤

使用JFileChooser来建立文件对话框并允许用户选择文件的步骤如下。

创建JFileChooser对象

  1. 采用构造器创建一个JFileChooser对象,该JFileChooser对象无须指定parent组件,这意味着可以在多个窗口中共用该JFileChooser对象。创建JFileChooser对象时可以指定初始化路径,如下代码所示。
1
2
//以当前路径创建文件选择器
JFileChooser chooser = new JFileChooser(".");

JFileChooser执行初始化操作

  1. 调用JFileChooser的一系列可选的方法对JFileChooser执行初始化操作。JChooser大致有如下几个常用方法。
指定默认选择的文件
方法 描述
void setSelectedFile(File file) 指定该文件选择器默认选择的文件
void setSelectedFiles(File[] selectedFiles) 指定该文件选择器默认选择多个文件

例如:

1
2
//默认选择当前路径下的123.jpg文件
chooser.setSelectedFile(new File("123.jpg"));

设置选择模式

方法 描述
void setFileSelectionMode(int mode) 在默认情况下,该文件选择器只能选择文件,通过调用该方法可以设置允许选择文件、路径、文件与路径,mode参数可以设置为:JFileChooser.FILES_ONLYJFileChooser.DIRECTORIES_ONLYJFileChooser.FILES_AND_DIRECTORIES

例如

1
2
// 设置既可选择文件,也可选择路径
chooser.setFileselectionMode (JFileChooser.FILES_AND_DIRECTORIES);

设置文件过滤器

如果让文件对话框实现文件过滤功能,则需要结合FileFilter类来进行文件过滤。JFileChooser提供了两个方法来安装文件过滤器。

方法 描述
void addChoosableFileFilter(FileFilter filter) 添加文件过滤器。通过该方法允许该文件对话框有多个文件过滤器。
void setFileFilter(FileFilter filter) 设置文件过滤器。一旦调用了该方法,将导致该文件对话框只有个文件过滤器。

设置文件视图

如果需要改变文件对话框中文件的视图外观,则可以结合FileView类来改变对话框中文件的视图外观。

打开文件对话框

调用showXxxDialog方法可以打开文件对话框,通常如下三个方法可用。

方法 描述
int showDialog(Component parent, String approveButtonText) 弹出文件对话框,该对话框的标题“同意”按钮的文本(默认是“保存”或“取消”按钮)由approveButtonText来指定。
int showOpenDialog(Component parent) 弹出文件对话框,该对话框具有默认标题,“同意”按钮的文本是“打开”。
int showSaveDialog(Component parent) 弹出文件对话框,该对话框具有默认标题,“同意”按钮的文本是“保存”。

当用户单击“同意”、“取消”按钮,或者直接关闭文件对话框时才可以关闭该文件对话框,关闭该对话框时返回一个int类型的值,分别是:JFileChooser.APPROVE_OPTIONJFileChooser.CANCEL_OPTIONJFileChooser.ERROR_OPTION

如果希望获得用户选择的文件,则通常应该先判断对话框的返回值是否为JFileChooser.APPROVE_OPTION,该选项表明用户单击了“打开”或者“保存”按钮

获取用户选择的文件

JFileChooser提供了如下两个方法来获取用户选择的文件或文件集。

方法 描述
File getSelectedFile() 返回用户选择的文件。
File[] getSelectedFiles() 返回用户选择的多个文件。

按上面的步骤,就可以正常地创建一个“打开文件”、“保存文件”对话框,整个过程非常简单。如果要使用FileFilter类来进行文件过滤,或者使用FileView类来改变文件的视图风格,则有一点麻烦。

FileFilter类

先看使用FileFilter类来进行文件过滤。Javajava.io包下提供了一个FileFilter接口,该接口主要用于作为File类的listFiles(FileFilter)方法的参数,也是一个进行文件过滤的接口。但此处需要使用位于javax.swing.Filechooser包下的FileFilter抽象类,该抽象类包含两个抽象方法。

方法 描述
abstract boolean accept(File f) 判断该过滤器是否接受给定的文件,只有被该过滤器接受的文件才可以在对应的文件对话框中显示出来。
abstract String getDescription() 返回该过滤器的描述性文本。

如果程序要使用FileFilter类进行文件过滤,则通常需要扩展该FileFilter类,并重写该类的两个抽象方法,重写accept()方法时就可以指定自己的业务规则,指定该文件过滤器可以接受哪些文件。

例如如下代码:

1
2
3
4
5
6
7
8
9
public boolean accept(File f){
//如果该文件是路径,则接受该文件
if (f.isDirectory())
return true;
// 只接受以.gif作为后缀的文件
if (f.getName().endswith(".gif"))
return true;
return false;
}

在默认情况下,JFileChooser总会在文件对话框的“文件类型”下拉列表中增加“所有文件”选项,但可以调用JFileChoosersetAcceptAllFileFilterUsed(false)来取消显示该选项

方法 描述
void setAcceptAllFileFilterUsed(boolean b) Determines whether the AcceptAll FileFilter is used as an available choice in the choosable filter list.

FileView

FileView类用于改变文件对话框中文件的视图风格,FileView类也是一个抽象类,通常程序需要扩展该抽象类,并有选择性地重写它所包含的如下几个抽象方法。

方法 描述
String getDescription(File f) 返回指定文件的描述
Icon getIcon(File f) 返回指定文件在FIleChooser对话框中的图标
String getName(File f) 返回指定文件的文件名
String getTypeDescription(File f) 返回指定文件所属文件类型的描述
Boolean isTraversable(File f) 当该文件是目录时,返回该目录是否是可遍历的

与重写FileFilter抽象方法类似的是,重写这些方法实际上就是为文件选择器对话框指定自定义的外观风格。通常可以通过重写getlcon()方法来改变文件对话框中的文件图标。

程序示例

下面程序是一个简单的图片查看工具程序,该程序综合使用了上面所介绍的各知识点。

上面程序中为“打开”菜单项指定事件监听器,当用户单击该菜单时,程序打开文件对话框,并将用户打开的图片文件使用Label在当前窗口显示出来。
重写了FileFilter类的accept()方法,该方法根据文件后缀来决定是否接受该文件,其要求是当该文件的后缀等于该文件过滤器的extensions集合的某一项元素时,则该文件是可接受的。
程序的①处代码禁用了JFileChooser中“所有文件”选项,从而让用户只能看到图片文件。

第四段粗体字代码用于重写FileView类的getlcon()方法,该方法决定JFileChooser对话框中文件文件夹的图标——图标文件就返回pict.Png图标,根文件夹就返回dsk.Png图标,而普通文件夹则返回folder.png图标
运行上面程序,单击“打开”菜单项,将看到如图12.9所示的对话框

图省略…

上面程序中的②处粗体字代码还用了JFileChooser类的setAccessory(JComponent new Accessory)方法为该文件对话框指定附件,附件将会被显示在文件对话框的右上角,如图12.9所示。该附件可以是任何Swing组件(甚至可以使用容器),本程序中使用一个JLabe组件作为该附件组件,该JLabel用于显示用户所选图片文件的预览图片。该功能的实现很简单:当用户选择的图片发生改变时,以用户所选文件创建ImageIcon,并将该ImageIcon设置成该Label的图标即可。

监听用户选择文件的变化

为了实现当用户选择图片发生改变时,附件组件的icon随之发生改变的功能,必须为JFileChooser添加事件监听器,该事件监听器负责监听该对话框中用户所选择文件的变化JComponent类中提供了个addPropertyChangeListener方法,该方法可以为该JFileChooser添加一个属性监听器,用于监听用户选择文件的变化。程序中第一段粗体字代码实现了用户选择文件发生改变时的事件处理器。

12.2.5 使用JToolBar创建工具条

构造器

Swing提供了JToolBar类来创建工具条:

方法 描述
JToolBar() Creates a new tool bar; orientation defaults to HORIZONTAL.
JToolBar(int orientation) Creates a new tool bar with the specified orientation.
JToolBar(String name) Creates a new tool bar with the specified name.
JToolBar(String name, int orientation) Creates a new tool bar with a specified name and orientation.

创建JToolBar对象时可以指定如下两个参数:

  • name:该参数指定该工具条的名称
  • orientation:该参数指定该工具条的方向。

JToolBar常用方法

一旦创建了JToolBar对象之后,JToolBar对象还有如下几个常用方法:

方法 描述
JButton add(Action a) 通过Action对象为JToolBar添加对应的工具按钮。
void addSeparator() 添加一个默认大小的分隔符
void addSeparator(Dimension size) 向工具条中添加指定大小的分隔符
void setFloatable(boolean b) 设置该工具条是否可浮动,即该工具条是否可以拖动
void setMargin(Insets m) 设置工具条边框和工具按钮之间的页边距
void setOrientation(int o) 设置工具条的方向
void setRollover(boolean rollover) 设置此工具条的rollover状态

Action接口

上面的大多数方法都比较容易理解,比较难以理解的是add方法,系统如何为工具条添加Action对应的按钮呢?

Action接口是ActionListener接口的子接口,它除包含ActionListener接口的actionPerformed()方法之外,还包含nameicon两个属性,其中name用于指定按钮或菜单项中的文本,而icon则用于指定按钮的图标或菜单项中的图标。也就是说,Action不仅可作为事件监听器使用,而且可被转换成按钮或菜单项

值得指出的是,Action本身并不是按钮,也不是菜单项,只是当把Action对象添加到某些容器(也可直接使用Action来创建按钮),如菜单和工具栏中时,这些容器会为该Action对象创建对应的组件(菜单项和按钮)。也就是说,这些容器需要负责完成如下事情。

  • 创建一个适用于该容器的组件(例如,在工具栏中创建一个工具按钮)
  • Action对象中获得对应的属性来设置该组件(例如,通过name来设置文本,通过icon来设置图标)。
  • 检查Action对象的初始状态,确定它是否处于激活状态,并根据该Action的状态米决定其对应所有组件的行为。只有处于激活状态的Action所对应的Swing组件才可以响应用户动作。
  • 通过Action对象为对应组件注册事件监听器,系统将为该Action所创建的所有组件注册同一个事件监听器(事件处理器就是Action对象里的actionPerformed方法)

程序

例如,程序中有一个菜单项、一个工具按钮,还有一个普通按钮都需要完成某个“复制”动作,程序就可以将该“复制”动作定义成Action,并为之指定nameicon属性,然后通过该Action来创建菜单项、工具按钮和普通按钮,就可以让这三个组件具有相同的功能。另一个“粘贴”按钮也大致相似,而且“粘贴”组件默认不可用,只有当“复制”组件被触发后,且剪贴板中有内容时才可用。

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
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import javax.swing.*;

public class JToolBarTest {
JFrame jFrame = new JFrame("测试工具条");
JTextArea jTextArea = new JTextArea(6, 35);
JToolBar jToolBar = new JToolBar();
JMenuBar jMenuBar = new JMenuBar();
JMenu editMenu = new JMenu("编辑");
// 获取系统剪贴板
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
// 创建"粘贴"Action,该Action用于创建菜单项、工具按钮和普通按钮
Action pasteAction = new AbstractAction("粘贴", new ImageIcon("ico/paste.png")) {
private static final long serialVersionUID = 3517594171860327637L;

public void actionPerformed(ActionEvent e) {
// 如果剪贴板中包含stringFlavor内容
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
try {
// 取出剪贴板中stringFlavor内容
String content = (String) clipboard.getData(DataFlavor.stringFlavor);
// 将选中内容替换成剪贴板中的内容
jTextArea.replaceRange(content, jTextArea.getSelectionStart(), jTextArea.getSelectionEnd());
} catch (Exception ee) {
ee.printStackTrace();
}
}
}
};
// 创建"复制"Action
Action copyAction = new AbstractAction("复制", new ImageIcon("ico/copy.png")) {
private static final long serialVersionUID = 8903877264455460708L;

public void actionPerformed(ActionEvent e) {
StringSelection contents = new StringSelection(jTextArea.getSelectedText());
// 将StringSelection对象放入剪贴板
clipboard.setContents(contents, null);
// 如果剪贴板中包含stringFlavor内容
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
// 将pasteAction激活
pasteAction.setEnabled(true);
}
}
};

public void init() {
// pasteAction默认处于不激活状态
pasteAction.setEnabled(false); // 1
jFrame.add(new JScrollPane(jTextArea));
// 以Action创建按钮,并将该按钮添加到Panel中
JButton copyBn = new JButton(copyAction);
JButton pasteBn = new JButton(pasteAction);
JPanel jp = new JPanel();
jp.add(copyBn);
jp.add(pasteBn);
jFrame.add(jp, BorderLayout.SOUTH);
// 向工具条中添加Action对象,该对象将会转换成工具按钮
jToolBar.add(copyAction);
jToolBar.addSeparator();
jToolBar.add(pasteAction);
// 向菜单中添加Action对象,该对象将会转换成菜单项
editMenu.add(copyAction);
editMenu.add(pasteAction);
// 将edit菜单添加到菜单条中
jMenuBar.add(editMenu);
jFrame.setJMenuBar(jMenuBar);
// 设置工具条和工具按钮之间的页边距。
jToolBar.setMargin(new Insets(5, 5, 5, 5)); // 2
// 向窗口中添加工具条
jFrame.add(jToolBar, BorderLayout.NORTH);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.pack();
jFrame.setVisible(true);
}

public static void main(String[] args) {
new JToolBarTest().init();
}
}

上面程序中创建了pasteActioncopyAction两个Action,然后根据这两个Action分别创建了按钮工具按钮菜单项组件,开始时pasteAction处于非激活状态,则该Action对应的按钮工具按钮菜单项都处于不可用状态。运行上面程序,会看到如图12.7所示的界面

这里有一张图片

图12.7显示了工具条被拖动后的效果,这是因为工具条默认处于浮动状态。除此之外,程序中 2 号代码设置了工具条和工具按钮之间的页边距,所以可以看到工具条在工具按钮周围保留了一些空白区域。

12.2.4 Swing组件的双缓冲和键盘驱动

除此之外,Swing组件还有如下两个功能:

  • 所有的Swing组件默认启用双缓冲绘图技术
  • 所有的Swing组件都提供了简单的键盘驱动

Swing组件默认启用双缓冲绘图技术,使用双缓冲技术能改进频繁重绘GUI组件的显示效果(避免闪烁现象)。JComponent组件默认启用双缓冲,无须自己实现双缓冲。如果想关闭双缓冲,可以在组件上调用setDoubleBuffered(false)方法。前一章介绍五子棋游戏时已经提到Swing组件的双缓冲技术,而且可以使用JPanel代替前一章所有示例程序中的Canvas画布组件,从而可以解决运行那些示例程序时的“闪烁”现象。

JComponent类提供了getInputMap()getActionMap()两个方法,其中

方法 描述
getInputMap() 返回一个InputMap对象,该对象用于将KeyStroke对象和名字关联,KeyStroke代表键盘或其他类似输入设备的一次输入事件;
getActionMap() 返回一个ActionMap对象,该对象用于将指定名字和Action关联,Action接口是ActionListener接口的子接口,可作为一个事件监听器使用,从而可以允许用户通过键盘操作来替代鼠标驱动GUI上的Swing组件,相当于为GUI组件提供快捷键

典型用法如下:

//把一次键盘事件和一个aCommand象关联
component.getInputMap().put(aKeystroke,aCommand);
//将 command对象和anAction事件响应关联
component.getActionMap().put(aCommmand,anAction);

程序 Swing快捷键

下面程序实现这样一个功能:用户在单行文本框内输入内容,当输入完成后,单击后面的“发送”按钮即可将文本框的内容添加到一个多行文本域中;或者输入完成后在文本框内按“Ctrl+Enter”键也可以将文本框的内容添加到一个多行文本域中。

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
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class BindKeyTest {
// 定义窗体
JFrame jFrame = new JFrame("测试键盘绑定");
// 文本域
JTextArea jTextArea = new JTextArea(5, 30);
// 按钮
JButton jButton = new JButton("发送");
// 文本框
JTextField jTextField = new JTextField(15);

public void init() {
// 添加文本框到窗体中
jFrame.add(jTextArea);
// 创建一个面板
JPanel jp = new JPanel();
// 添加文本框到面板中
jp.add(jTextField);
// 添加按钮到文本框中
jp.add(jButton);
// 添加面包到窗体的南边(上北下南)
jFrame.add(jp, BorderLayout.SOUTH);

// 创建一个Action
// 发送消息的Action,Action是ActionListener的子接口
Action sendMsg = new AbstractAction() {
// 设置序列号值
private static final long serialVersionUID = 2625520225836946219L;
// 实现方法
public void actionPerformed(ActionEvent e) {
// 把文本框中的文本追加到文本域中
jTextArea.append(jTextField.getText() + "\n");
// 清空文本框中的内容
jTextField.setText("");
}
};
// 添加事件监听器
jButton.addActionListener(sendMsg);

// 将Ctrl+Enter键和 字符串"send" 关联
jTextField.getInputMap().put(KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_DOWN_MASK), "send");
// 将 字符串 "send" 和sendMsg这个Action关联
jTextField.getActionMap().put("send", sendMsg);

// 以最小方式显示窗体
jFrame.pack();
// 设置窗体可见
jFrame.setVisible(true);
}

public static void main(String[] args) {
new BindKeyTest().init();
}
}

上面程序中的代码:

// 将Ctrl+Enter键和 字符串"send" 关联
jTextField.getInputMap().put(KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_DOWN_MASK), "send");
// 将 字符串 "send" 和sendMsg这个Action关联
jTextField.getActionMap().put("send", sendMsg);

示范了如何利用键盘事件来驱动Swing组件,采用这种键盘事件机制,无须为Swing组件绑定键盘监听器,从而可以复用按钮单击事件的事件监听器,程序十分简洁。

12.2.3 为组件设置边框

可以调用JComponent提供的setBorder(Border b)方法为Swing组件设置边框,其中BorderSwing提供的一个接口,用于代表组件的边框。该接口有数量众多的实现类,如LineBorderMatteBorderBevelBorder等,这些Border实现类都提供了相应的构造器用于创建Border对象,一旦获取了Border对象之后,就可以调用JComponentsetBorder(Border b)方法为指定组件设置边框。

TitledBorderCompoundBorder比较独特,其中TitledBorder的作用并不是为其他组件添加边框,而是为其他边框设置标题,当创建TitledBorder对象时,需要传入一个已经存在的Border对象,新创建的TitledBorder对象会为原有的Border对象添加标题;而CompoundBorder用于组合两个边框,因此创建CompoundBorder对象时需要传入两个Border对象,一个用作组件的内边框,一个用作组件的外边框。

除此之外,Swing还提供了一个BorderFactory静态工厂类,该类提供了大量的静态工厂方法用于返回Border实例,这些静态方法的参数与各Border实现类的构造器参数基本一致。

Border不仅提供了上面所提到的一些Border实现类,还提供了 MetalBorders.ToolBarBorderMetalBorders.TextFieldBorderBorder实现类,这些实现类用作Swing组件的默认边框,程序中通常无须使用这些系统边框。

Swing组件添加边框可按如下步骤进行:

  • 使用BorderFactory或者XxxBorder创建XxxBorder实例。
  • 调用Swing组件的setBorder(Border b)方法为该组件设置边框

图12.5显示了系统可用边框之间的继承层次

这里有一张图片

程序 为Panel容器添加边框

下面的例子程序示范了为Panel容器分别添加如图12.5所示的几种边框。

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
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;

public class BorderTest {
private JFrame jf = new JFrame("测试边框");

public void init() {
jf.setLayout(new GridLayout(2, 4));
// 使用静态工厂方法创建BevelBorder
Border bb = BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.RED, Color.GREEN, Color.BLUE, Color.GRAY);
jf.add(getPanelWithBorder(bb, "BevelBorder"));
// 使用静态工厂方法创建LineBorder
Border lb = BorderFactory.createLineBorder(Color.ORANGE, 10);
jf.add(getPanelWithBorder(lb, "LineBorder"));
// 使用静态工厂方法创建EmptyBorder,EmptyBorder就是在组件四周留空
Border eb = BorderFactory.createEmptyBorder(20, 5, 10, 30);
jf.add(getPanelWithBorder(eb, "EmptyBorder"));
// 使用静态工厂方法创建EtchedBorder
Border etb = BorderFactory.createEtchedBorder(EtchedBorder.RAISED, Color.RED, Color.GREEN);
jf.add(getPanelWithBorder(etb, "EtchedBorder"));
// 直接创建TitledBorder,TitledBorder边框就是为原有的边框增加标题
TitledBorder tb = new TitledBorder(lb, "测试标题", TitledBorder.LEFT, TitledBorder.BOTTOM,
new Font("StSong", Font.BOLD, 18), Color.BLUE);
jf.add(getPanelWithBorder(tb, "TitledBorder"));
// 直接创建MatteBorder,MatteBorder边框是EmptyBorder的子类,
// 它可以指定留空区域的颜色或背景,此处是指定颜色
MatteBorder mb = new MatteBorder(20, 5, 10, 30, Color.GREEN);
jf.add(getPanelWithBorder(mb, "MatteBorder"));
// 直接创建CompoundBorder,CompoundBorder边框将两个边框组合成新边框
CompoundBorder cb = new CompoundBorder(new LineBorder(Color.RED, 8), tb);
jf.add(getPanelWithBorder(cb, "CompoundBorder"));
jf.pack();
jf.setVisible(true);
}

public static void main(String[] args) {
new BorderTest().init();
}

public JPanel getPanelWithBorder(Border b, String BorderName) {
JPanel p = new JPanel();
p.add(new JLabel(BorderName));
// 为Panel组件设置边框
p.setBorder(b);
return p;
}
}

运行上面程序,会看到如图12.6所示的效果。

这里有一张图片

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组件的标题栏。

12.2 Swing基本组件的用法

前面已经提到,Swing为所有的AWT组件提供了对应实现(除Canvas组件之外,因为在Swing中无须继承Canvas组件),通常在AWT组件的组件名前添加“J”就变成了对应的Swing组件

12.2.1 Java的Swing组件层次

大部分Swing组件都是JComponent抽象类的直接或间接子类(并不是全部的Swing组件)JComponent类定义了所有子类组件的通用方法,JComponent类是AWTJava.AwtContainer类的子类,这也是AWTSwing的联系之一。绝大部分Swing组件类继承了Container类,所以Swing组件都可作为容器使用(JFrame继承了Frame类)。图12.1显示了Swing组件继承层次图。

图12.1

图12.1中绘制了Swing所提供的绝大部分组件,其中以灰色区域覆盖的组件可以找到与之对应的AWT组件;JWindowAWT中的Window相似,代表没有标题的窗口。读者不难发现这些Swing组件的类名和对应AWT组件的类型也基本一致,只要在原来的AWT组件类型前添加“J”即可,但有如下几个例外:

  • JComboBox:对应于AWT里的Choice组件,但比Choice组件功能更丰富。
  • JFileChooser:对应于AWT里的FileDialog组件。
  • JScrollBar:对应于AWT里的Scrollbar组件,注意两个组件类名中b字母的大小写差别
  • JCheckBox:对应于AWT里的Checkbox组件,注意两个组件类名中b字母的大小写差别。
  • JCheckBoxMenuitem:对应于AWT里的CheckboxMenuitem组件,注意两个组件类名中b字母的大小写差别。

上面JCheckBoxJCheckBoxMenuitemCheckboxCheckboxMenuitem的差别主要是由早期Java命名不太规范造成的。

Swing中的重量级组件

从图12.1中可以看出,Swing中包含了4个组件直接继承了AWT组件,而不是从JComponent派生的,它们分别是:JFrameJWindowJDialogJApplet,它们并不是轻量级组件,而是重量级组件(需要部分委托给运行平台上GUI组件的对等体)。

Swing组件分类

Swing组件按功能来分,又可分为如下几类

  • 顶层容器:JFrameJAppletJDialogJWindow
  • 中间容器:JPanelJScrollPaneJSplitPaneJToolBar
  • 特殊容器:在用户界面上具有特殊作用的中间容器,如JInternalFrameJRootPaneJLayeredPaneJDestopPane等。
  • 基本组件:实现人机交互的组件,如JButtonJComboBoxJListJMenuJSlider等。
  • 不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabelJProgressBarJToolTip
  • 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如JTableJTextAreaJTextField等。
  • 特殊对话框组件:可以直接产生特殊对话框的组件,如JColorChooserJFileChooser等。

12.1 Swing概述

前一章已经介绍过AWTSwing的关系,因此不难知道:实际使用Java开发图形界面程序时,很少使用AWT组件,绝大部分时候都是用Swing组件开发的。Swing是由100%纯Java实现的,不再依赖于本地平台的GUI,因此可以在所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为轻量级组件;而依赖于本地平台的AWT组件被称为重量级组件。

Swing相应速度慢一点

由于Swing的所有组件完全采用Java实现,不再调用本地平台的GUI,所以导致Swing图形界面的显示速度要比AWT图形界面的显示速度慢一些,但相对于快速发展的硬件设施而言,这种微小的速度差别无妨大碍。

Swing优势

使用Swing开发图形界面有如下几个优势:

  • Swing组件不再依赖于本地平台的GUI,无须采用各种平台的GUI交集,因此Swing提供了大量图形界面组件,远远超出了AWT所提供的图形界面组件集。
  • Swing组件不再依赖于本地平台GUI,因此不会产生与平台相关的bug
  • Swing组件在各种平台上运行时可以保证具有相同的图形界面外观。

Swing特征

Swing提供的这些优势,让Java图形界面程序真正实现了“Write Once,Run Anywhere”的目标。除此之外,Swing还有如下两个特征。

MVC

Swing组件采用MVC(Model-View-Controller,即模型一视图一控制器)设计模式,其中模型(Model)用于维护组件的各种状态,

  • 视图(view)是组件的可视化表现,
  • 控制器(Controller用于控制对于各种事件、组件做出怎样的响应。
  • 当模型发生改变时,它会通知所有依赖它的视图,视图会根据模型数据来更新自己。

模型对象

Swing使用UI代理来包装视图和控制器,还有另一个模型对象来维护该组件的状态。例如,按钮JButton有一个维护其状态信息的模型ButtonModel对象。Swing组件的模型是自动设置的,因此一般都使用JButton,而无须关心ButtonModel对象。因此,SwingMVC实现也被称为Model-Delegate(模型-代理)。

对于一些简单的Swing组件通常无须关心它对应的Model对象,但对于一些高级的Swing组件,如JTreeJTable等需要维护复杂的数据,这些数据就是由该组件对应的Model来维护的。另外,通过创建Model类的子类或通过实现适当的接口,可以为组件建立自己的模型,然后用setModel()方法把模型与组件关联起来。

插拔式外观感觉

Swing在不同的平台上表现一致,并且有能力提供本地平台不支持的显示外观。由于Swing组件采用MVC模式来维护各组件,所以Swing组件的外观被改变时,对组件的状态信息(由模型维护)没有任何影响。因此,Swing可以使用插拔式外观感觉(Pluggable Look And Feel,PLAF)。来控制组件外观,使得Swing图形界面在同一个平台上运行时能拥有不同的外观,用户可以选择自己喜欢的外观。相比之下,在AWT图形界面中,由于控制组件外观的对等类与具体平台相关,因此AWT组件总是具有与本地平台相同的外观。

LAF

Swing提供了多种独立于各种平台的LAF(Look And feel),默认是一种名为MetalLAF,这种LAF吸收了Macintosh平台的风格,因此显得比较漂亮。Java7则提供了一种名为NimbusLAF,这种LAF更加漂亮。

程序 获取当前JRE所支持的LAF

为了获取当前JRE所支持的LAF,可以借助于UIManagergetInstalledFeels()方法,如下程序所示。

1
2
3
4
5
6
7
8
9
10
import javax.swing.*;

public class AllLookAndFeel {
public static void main(String[] args) {
System.out.println("当前系统可用的所有LAF:");
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
System.out.println(info.getName() + "--->" + info);
}
}
}

运行结果

1
2
3
4
5
Metal--->javax.swing.UIManager$LookAndFeelInfo[Metal javax.swing.plaf.metal.MetalLookAndFeel]
Nimbus--->javax.swing.UIManager$LookAndFeelInfo[Nimbus javax.swing.plaf.nimbus.NimbusLookAndFeel]
CDE/Motif--->javax.swing.UIManager$LookAndFeelInfo[CDE/Motif com.sun.java.swing.plaf.motif.MotifLookAndFeel]
Windows--->javax.swing.UIManager$LookAndFeelInfo[Windows com.sun.java.swing.plaf.windows.WindowsLookAndFeel]
Windows Classic--->javax.swing.UIManager$LookAndFeelInfo[Windows Classic com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel]

除可以使用Java默认提供的数量不多的几种LAF之外,还有大量的Java爱好者提供了各种开源的LAF,有兴趣的读者可以自行去下载、体验各种LAF,使用不同的LAF可以让Swing应用程序更加美观。

12.0 本章概述

本章要点

  • Swing编程基础
  • Swing组件的继承层次
  • 常见Swing组件的用法
  • 使用JToolBar创建工具条
  • 颜色选择对话框和文件浏览对话框
  • Swing提供的特殊容器
  • Swing的简化拖放操作
  • 使用JLayer装饰组件
  • 开发透明的、不规则形状窗口
  • 开发进度条
  • 开发滑动条
  • 使用JTreeTreeModel开发树
  • 使用JTableTableModel开发表格
  • 使用JTextPane组件

Swing跨平台

使用Swing开发图形界面比AWT更加优秀,因为Swing是一种轻量级组件,Swing采用100%的Java实现,不再依赖于本地平台的图形界面,所以可以在所有平台上保持相同的运行效果,对跨平台支持比较出色

Swing组件更多

除此之外,Swing提供了比AWT更多的图形界面组件,因此可以开发出更美观的图形界面。由于AWT需要调用底层平台的GUI实现,所以AWT只能使用各种平台上GUI组件的交集,这大大限制了AWT所支持的GUI组件。对Swing而言,几乎所有组件都采用纯Java实现,所以无须考虑底层平台是否支持该组件,因此Swing可以提供如JTabbedPaneJDesktopPaneJInternalFrame等特殊的容器,也可以提供像JTreeJTableJSpinnerJSlider等特殊的GUI组件。

MVC

除此之外,Swing组件都釆用MVC(Model-View-Controller,即模型-视图-控制器)设计模式,从而可以实现GUI组件的显示逻辑和数据逻辑的分离,允许程序员自定义Render来改变GUI组件的显示外观,提供更多的灵活性。

11.11 本章小结

本章主要介绍了Java AWT编程的基本知识,虽然在实际开发中很少直接使用AWT组件来开发GUI应用,但本章所介绍的知识会作为Swing GUI编程的基础。实际上,AWT编程的布局管理、事件机制、剪贴板内容依然适合Swing GUI编程,所以读者应好好掌握本章内容。

本章介绍了Java GUI界面编程以及AWT的基本概念,详细介绍了AWT容器和布局管理器。本章重点介绍了Java GUI编程的事件机制,详细描述了事件源、事件、事件监听器之间的运行机制,AWT的事件机制也适合Swing的事件处理。除此之外,本章也大致介绍了AWT里的常用组件,如按钮、文本框、对话框、菜单等。本章还介绍了如何在Java程序中绘图,包括绘制各种基本几何图形和绘制位图,并通过简单的弹球游戏介绍了如何在Java程序中实现动画效果。

本章最后介绍了Java剪贴板的用法,通过使用剪贴板,可以让Java程序和操作系统进行数据交换,
从而允许把Java程序的数据传入平台中的其他程序,也可以把其他程序中的数据传入Java程序。

本章练习

  1. 开发图形界面计算器。
  2. 开发桌面弹球游戏。
  3. 开发Windows画图程序。
  4. 开发图形界面五子棋。