Swing提供了丰富的控件供我们构建复杂的表单和数据展示界面,这篇笔记我们以例子代码的方式对Swing中常用的表单和数据展示控件进行介绍,当然这里不可能逐一列举所有控件的所有属性,有关这些细节我们用到时参考JDK的标准文档即可。
JButton
是最通用的基础按钮,前面我们已经详细介绍过了,除了JButton
,Swing还提供了JToggleButton
切换按钮和JRadioButton
单选按钮。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
JButton btn = new JButton("JButton");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("JButton Clicked!");
}
});
final JToggleButton tbtn = new JToggleButton("OFF");
tbtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (tbtn.isSelected()) {
tbtn.setText("ON");
} else {
tbtn.setText("OFF");
}
}
});
ButtonGroup group = new ButtonGroup();
JRadioButton rbtn1 = new JRadioButton("Option 1");
rbtn1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Option 1 selected!");
}
});
JRadioButton rbtn2 = new JRadioButton("Option 2");
rbtn2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Option 2 selected!");
}
});
JRadioButton rbtn3 = new JRadioButton("Option 3");
rbtn3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Option 3 selected!");
}
});
group.add(rbtn1);
group.add(rbtn2);
group.add(rbtn3);
pMain.add(btn);
pMain.add(tbtn);
pMain.add(rbtn1);
pMain.add(rbtn2);
pMain.add(rbtn3);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们分别创建了普通按钮、切换按钮和单选按钮,并绑定了对应的事件,其中单选按钮比较特殊,它需要一个按钮组对象ButtonGroup
,否则Swing不知道要在哪个范围内进行单项选择的控制。程序运行效果如下。
JCheckBox
可以实现复选框,复选框通常使用ItemListener
的itemStateChanged
事件监听,复选框被触发后有选中和取消选中两种状态,这可以通过控件的isSelected()
方法判断。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
final JCheckBox cb1 = new JCheckBox("Option 1");
final JCheckBox cb2 = new JCheckBox("Option 2");
final JCheckBox cb3 = new JCheckBox("Option 3");
cb1.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (cb1.isSelected()) {
System.out.println("Option 1 selected!");
} else {
System.out.println("Option 1 unselected!");
}
}
});
cb2.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (cb1.isSelected()) {
System.out.println("Option 2 selected!");
} else {
System.out.println("Option 2 unselected!");
}
}
});
cb3.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (cb1.isSelected()) {
System.out.println("Option 3 selected!");
} else {
System.out.println("Option 3 unselected!");
}
}
});
pMain.add(cb1);
pMain.add(cb2);
pMain.add(cb3);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们创建了3个复选框并绑定了事件,回调代码中我们在选中和取消选中时输出了不同的内容,程序运行效果如下。
JComboBox
用于创建下拉列表,它需要使用一个数组来初始化,数组内通常使用字符串或ImageIcon
。
package com.gacfox.demo;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
String[] items = new String[] { "Tom", "Jerry", "Lucy" };
final JComboBox cbCharacter = new JComboBox(items);
cbCharacter.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String selected = (String) cbCharacter.getSelectedItem();
System.out.println(selected);
}
});
pMain.add(cbCharacter);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们使用包含3个选项的字符串数组初始化了JComboBox
,并为其绑定了事件,当选择了一个下拉选项时对应回调函数会被触发并打印选择的选项字符串,运行效果如下。
此外,我们也可以使用JComboBox
的addItem()
方法动态更新选项。
JTextField
、JPasswordField
分别用于单行的文本输入和密码输入,JTextArea
则可用于多行文本输入,下面是一些例子。
package com.gacfox.demo;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MainFrame extends JFrame {
public MainFrame() {
Box vbMain = Box.createVerticalBox();
vbMain.setBorder(new EmptyBorder(5, 5, 5, 5));
final JTextField tfUsername = new JTextField(15);
final JPasswordField pfPassword = new JPasswordField(15);
final JTextArea taText = new JTextArea(2, 15);
JButton btnSubmit = new JButton("Submit");
btnSubmit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Username: " + tfUsername.getText());
System.out.println("Password: "
+ new String(pfPassword.getPassword()));
System.out.println("Text: " + taText.getText());
}
});
vbMain.add(tfUsername);
vbMain.add(pfPassword);
vbMain.add(taText);
vbMain.add(btnSubmit);
add(vbMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们分别创建了文本输入框、密码输入框和文本域,我们还添加了一个按钮,点击按钮时将打印所有文本框中的内容。
JSlider
用于创建滑块,它能在一定范围内通过滑块的滑动选取数值,下面是一个例子。
package com.gacfox.demo;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
final JSlider sValuePicker = new JSlider(JSlider.HORIZONTAL, 0, 100, 0);
final JLabel lValue = new JLabel("0");
sValuePicker.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int value = sValuePicker.getValue();
lValue.setText(String.valueOf(value));
}
});
pMain.add(sValuePicker);
pMain.add(lValue);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们使用了4个参数构造JSlider
对象,分别是滑条方向、起始值、结束值和初始值,滑块滑动时ChangeListener
的stateChanged
方法将被回调,程序运行效果如下。
JSpinner
是一种数值选择器控件,它不仅支持整数,还支持选项和日期,这三种类型是通过指定不同的SpinnerModel
实现的。
package com.gacfox.demo;
import java.util.Calendar;
import java.util.Date;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class MainFrame extends JFrame {
public MainFrame() {
JPanel pMain = new JPanel();
SpinnerNumberModel numberModel = new SpinnerNumberModel(0, 0, 100, 1);
final JSpinner s1 = new JSpinner(numberModel);
String[] items = { "Option 1", "Option 2", "Option 3" };
SpinnerListModel listModel = new SpinnerListModel(items);
final JSpinner s2 = new JSpinner(listModel);
SpinnerDateModel dateModel = new SpinnerDateModel(new Date(), null,
null, Calendar.DAY_OF_MONTH);
final JSpinner s3 = new JSpinner(dateModel);
s1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
System.out.println("s1: " + s1.getValue());
}
});
s2.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
System.out.println("s2: " + s2.getValue());
}
});
s3.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
System.out.println("s3: " + s3.getValue());
}
});
pMain.add(s1);
pMain.add(s2);
pMain.add(s3);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中我们分别创建了整数值、选项和日期的数值选择器。整数值模型使用SpinnerNumberModel
,其参数为初始值、最小值、最大值和步长;选项类型使用SpinnerListModel
,它使用一个字符串数组初始化;日期类型使用SpinnerDateModel
实现,参数分别是初始值、最小值、最大值和默认递增或递减的日期字段。
在界面中,点击数值切换按钮可以看到数值的递增或递减,对于日期类型的数值选择器,我们可以鼠标单击选中一个日期字段,并通过切换按钮对其进行操作。
JProgressBar
用于创建进度条,进度条可以包含一个不断推进的进度指示器和对应的文本说明,下面是一个例子。
package com.gacfox.demo;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class MainFrame extends JFrame {
private int progress = 0;
public MainFrame() {
JPanel pMain = new JPanel();
final JProgressBar progressBar = new JProgressBar(0, 100);
progressBar.setValue(0);
progressBar.setStringPainted(true);
progressBar.setString("0%");
JButton btnStart = new JButton("START");
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
progress = 0;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressBar.setValue(progress);
progressBar.setString(progress + "%");
progress++;
}
});
}
}
});
thread.start();
}
});
pMain.add(btnStart);
pMain.add(progressBar);
add(pMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们创建了一个按钮和一个进度条,点击按钮后会触发一个新线程,它每隔100ms将进度值增加1。注意,类似线程睡眠、网络和磁盘IO等阻塞操作绝不应该出现在UI线程中,这些操作可能造成GUI界面的“假死”和“冻结”现象,事件循环的阻塞甚至可能让Windows操作系统认为我们的图形界面应用程序已经卡死了,Windows会将其标记为“无响应”并弹出提示让用户“立即结束”该进程。这里我们通过创建新线程的方式编写这些阻塞逻辑,但更新GUI控件又必须放在UI线程,因此我们这里使用了SwingUtilities.invokeLater()
将进度条的更新放在了UI线程中。
JList
是Swing中一个十分强大的控件,它支持动态的展示列数据。下面例子代码我们实现了一个Todo
程序,我们可以在输入框中输入内容,点击按钮后数据将被提交并动态显示在列表中。
package com.gacfox.demo;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class MainFrame extends JFrame {
private DefaultListModel todoListModel = new DefaultListModel();
public MainFrame() {
JList lTodo = new JList(todoListModel);
JScrollPane spTodo = new JScrollPane(lTodo);
add(spTodo);
JPanel pInput = new JPanel();
final JTextField tfTodo = new JTextField(20);
JButton btnSubmit = new JButton("Submit");
btnSubmit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String text = tfTodo.getText();
todoListModel.addElement(text);
}
});
pInput.add(tfTodo);
pInput.add(btnSubmit);
add(pInput, BorderLayout.SOUTH);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
程序运行结果如下。
JTable
用于构建表格,它的用法和JList
类似,但是表格的数据多了一个维度,这也让表格展现的数据更复杂但功能也更强大。这里我们将之前的Todo
应用程序修改为表格展示,在数据库添加一组时间列。
package com.gacfox.demo;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class MainFrame extends JFrame {
private SimpleDateFormat format = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
private DefaultTableModel todoTableModel = new DefaultTableModel(
new String[][] {}, new String[] { "Content", "Create Time" });
public MainFrame() {
JTable tTodo = new JTable(todoTableModel);
JScrollPane spTodo = new JScrollPane(tTodo);
add(spTodo);
JPanel pInput = new JPanel();
final JTextField tfTodo = new JTextField(20);
JButton btnSubmit = new JButton("Submit");
btnSubmit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String text = tfTodo.getText();
String time = format.format(new Date());
todoTableModel.addRow(new String[] { text, time });
}
});
pInput.add(tfTodo);
pInput.add(btnSubmit);
add(pInput, BorderLayout.SOUTH);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,DefaultTableModel
的初始化需要初始值和列名(作为表头),对其执行添加内容操作时,添加的也是字符串数组。
JTree
用于展示树形数据,经常用于菜单展示,下面是一个例子。
package com.gacfox.demo;
import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
public class MainFrame extends JFrame {
public MainFrame() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
DefaultMutableTreeNode node1 = new DefaultMutableTreeNode("Node 1");
DefaultMutableTreeNode node2 = new DefaultMutableTreeNode("Node 2");
DefaultMutableTreeNode node3 = new DefaultMutableTreeNode("Node 3");
root.add(node1);
root.add(node2);
node2.add(node3);
DefaultTreeModel model = new DefaultTreeModel(root);
JTree tree = new JTree(model);
tree.addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath path = e.getNewLeadSelectionPath();
if (path != null) {
System.out.println("Selected: "
+ path.getLastPathComponent());
}
}
});
JScrollPane spMain = new JScrollPane(tree);
add(spMain);
pack();
setLocationByPlatform(true);
setTitle("Hello, Swing!");
}
}
代码中,我们初始化了一个树形结构,并将其设置给JTree
控件,此外我们还为树控件绑定了节点选择的事件,点选一个节点时,控制台会输出对应的节点文本数据。程序运行结果如下。
默认情况下,JTree
中的节点都是支持展开和收起的。