Java Swing编程入门:原理、组件与事件处理
1. 可插拔外观与感觉
在Java编程中,可插拔外观与感觉(Pluggable look-and-feels)代表了不同的GUI风格。使用特定风格时,只需简单地“插入”对应的外观与感觉,之后所有组件都会自动以该风格渲染。
1.1 可插拔外观与感觉的优势
- 跨平台一致性 :能够定义在所有平台上保持一致的外观与感觉。
- 特定平台模拟 :可以创建类似特定平台的外观与感觉,例如在Windows环境下指定Windows外观与感觉。
- 自定义设计 :支持设计自定义的外观与感觉。
- 运行时动态更改 :能够在运行时动态改变外观与感觉。
1.2 Java 8提供的外观与感觉
Java 8为所有Swing用户提供了多种外观与感觉,如metal和Nimbus。其中,metal外观与感觉也被称为Java外观与感觉,它是平台独立的,在所有Java执行环境中都可用,并且是默认的外观与感觉。Windows环境还可以使用Windows外观与感觉。
2. MVC架构与Swing的关系
2.1 传统MVC架构
一个可视化组件通常由三个不同方面组成:
-
外观
:组件在屏幕上的呈现方式。
-
用户交互响应
:组件对用户操作的反应方式。
-
状态信息
:与组件相关的状态信息。
传统的MVC(Model-View-Controller)架构将组件分为模型、视图和控制器三个部分:
-
模型
:对应组件的状态信息,例如复选框的模型包含一个字段,用于指示复选框是否被选中。
-
视图
:决定组件在屏幕上的显示方式,包括受模型当前状态影响的视图方面。
-
控制器
:确定组件对用户的反应方式,例如当用户点击复选框时,控制器会更改模型以反映用户的选择,进而更新视图。
2.2 Swing的改进MVC架构
虽然MVC架构及其背后的原理在概念上是合理的,但视图和控制器之间的高度分离对Swing组件并不有利。因此,Swing使用了一种改进的MVC版本,将视图和控制器组合成一个称为UI委托(UI delegate)的逻辑实体。这种方法被称为模型 - 委托架构(Model-Delegate architecture)或可分离模型架构(Separable Model architecture)。
2.3 模型 - 委托架构的优势
Swing的可插拔外观与感觉得益于其模型 - 委托架构。由于视图(外观)和控制器(感觉)与模型分离,因此可以在不影响组件在程序中使用方式的情况下更改外观与感觉,反之亦然。
2.4 支持模型 - 委托架构的组件对象
大多数Swing组件包含两个对象:
-
模型
:由接口定义,例如按钮的模型由ButtonModel接口定义。
-
UI委托
:继承自ComponentUI的类,例如按钮的UI委托是ButtonUI。通常,程序不会直接与UI委托交互。
3. 组件与容器
3.1 组件与容器的概念
Swing GUI由组件和容器两个关键部分组成。实际上,所有容器也是组件,它们的区别在于用途:
-
组件
:通常是独立的可视化控件,如按钮或滑块。
-
容器
:用于容纳一组组件,是一种特殊的组件,所有组件必须包含在容器中才能显示。因此,所有Swing GUI至少有一个容器,并且容器可以嵌套,形成所谓的包含层次结构,其顶部必须是顶级容器。
3.2 组件
一般来说,Swing组件继承自JComponent类(四个顶级容器除外)。JComponent提供了所有组件共有的功能,如支持可插拔外观与感觉,并且继承了AWT的Container和Component类,因此Swing组件基于AWT组件构建并与之兼容。
Swing的所有组件由javax.swing包中的类表示,常见的组件类名如下表所示:
| 组件类名 | 说明 |
| ---- | ---- |
| JApplet | 小程序容器 |
| JButton | 按钮 |
| JCheckBox | 复选框 |
| JCheckBoxMenuItem | 复选框菜单项 |
| JColorChooser | 颜色选择器 |
| JComboBox | 下拉列表框 |
| JComponent | 组件基类 |
| JDesktopPane | 桌面面板 |
| JDialog | 对话框 |
| JEditorPane | 编辑器面板 |
| JFileChooser | 文件选择器 |
| JFormattedTextField | 格式化文本框 |
| JFrame | 窗口 |
| JInternalFrame | 内部窗口 |
| JLabel | 标签 |
| JLayer | 层 |
| JLayeredPane | 分层面板 |
| JList | 列表 |
| JMenu | 菜单 |
| JMenuBar | 菜单栏 |
| JMenuItem | 菜单项 |
| JOptionPane | 选项面板 |
| JPanel | 面板 |
| JPasswordField | 密码框 |
| JPopupMenu | 弹出菜单 |
| JProgressBar | 进度条 |
| JRadioButton | 单选按钮 |
| JRadioButtonMenuItem | 单选菜单项 |
| JRootPane | 根面板 |
| JScrollBar | 滚动条 |
| JScrollPane | 滚动面板 |
| JSeparator | 分隔符 |
| JSlider | 滑块 |
| JSpinner | 微调器 |
| JSplitPane | 分割面板 |
| JTabbedPane | 选项卡面板 |
| JTable | 表格 |
| JTextArea | 文本区域 |
| JTextField | 文本框 |
| JTextPane | 文本面板 |
| JTogglebutton | 切换按钮 |
| JToolBar | 工具栏 |
| JToolTip | 工具提示 |
| JTree | 树 |
| JViewport | 视口 |
| JWindow | 窗口 |
3.3 容器
Swing定义了两种类型的容器:
-
顶级容器
:包括JFrame、JApplet、JWindow和JDialog。这些容器不继承JComponent,但继承了AWT的Component和Container类。与Swing的其他轻量级组件不同,顶级容器是重量级的。顶级容器必须位于包含层次结构的顶部,每个包含层次结构都必须以顶级容器开始。其中,JFrame常用于应用程序,JApplet用于小程序。
-
轻量级容器
:继承自JComponent,例如JPanel是一个通用的轻量级容器,常用于组织和管理相关组件组,因为轻量级容器可以包含在其他容器中。
3.4 顶级容器的面板
每个顶级容器定义了一组面板,层次结构的顶部是JRootPane实例。JRootPane是一个轻量级容器,用于管理其他面板,并帮助管理可选的菜单栏。组成根面板的面板包括玻璃面板(glass pane)、内容面板(content pane)和分层面板(layered pane)。
-
玻璃面板
:是顶级面板,位于所有其他面板之上并完全覆盖它们。默认情况下,它是一个透明的JPanel实例,可用于管理影响整个容器的鼠标事件或在其他组件上绘制。
-
分层面板
:是JLayeredPane的实例,允许为组件指定深度值,以确定哪个组件覆盖另一个组件。
-
内容面板
:是应用程序与之交互最多的面板,因为可视化组件将添加到该面板中。默认情况下,内容面板是一个不透明的JPanel实例。
4. Swing包
Swing是一个非常大的子系统,使用了许多包,主要的包是javax.swing,任何使用Swing的程序都必须导入该包,它包含实现基本Swing组件的类,如按钮、标签和复选框。以下是Swing定义的部分包:
- javax.swing
- javax.swing.plaf.basic
- javax.swing.text
- javax.swing.border
- javax.swing.plaf.metal
- javax.swing.text.html
- javax.swing.colorchooser
- javax.swing.plaf.multi
- javax.swing.text.html.parser
- javax.swing.event
- javax.swing.plaf.nimbus
- javax.swing.text.rtf
- javax.swing.filechooser
- javax.swing.plaf.synth
- javax.swing.tree
- javax.swing.plaf
- javax.swing.table
- javax.swing.undo
5. 简单的Swing应用程序
5.1 示例代码
// A simple Swing application.
import javax.swing.*;
class SwingDemo {
SwingDemo() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("A Simple Swing Application");
// Give the frame an initial size.
jfrm.setSize(275, 100);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JLabel jlab = new JLabel(" Swing means powerful GUIs.");
// Add the label to the content pane.
jfrm.add(jlab);
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new SwingDemo();
}
});
}
}
5.2 代码解释
- 导入包 :程序开始时导入javax.swing包,该包包含Swing定义的组件和模型。
-
创建JFrame容器
:使用
JFrame jfrm = new JFrame("A Simple Swing Application");创建一个带有标题栏、关闭、最小化、最大化和恢复按钮以及系统菜单的标准顶级窗口。 -
设置窗口大小
:使用
jfrm.setSize(275, 100);设置窗口的初始大小。 -
设置默认关闭操作
:使用
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);确保当用户关闭窗口时,整个应用程序终止。 -
创建标签
:使用
JLabel jlab = new JLabel(" Swing means powerful GUIs.");创建一个显示文本信息的标签。 -
添加标签到内容面板
:使用
jfrm.add(jlab);将标签添加到框架的内容面板中。 -
显示窗口
:使用
jfrm.setVisible(true);使窗口可见。 -
在事件调度线程中创建窗口
:在
main方法中,使用SwingUtilities.invokeLater方法在事件调度线程中创建SwingDemo对象,以避免潜在的死锁问题。
5.3 编译和运行
Swing程序的编译和运行方式与其他Java应用程序相同:
- 编译:
javac SwingDemo.java
- 运行:
java SwingDemo
6. 事件处理
6.1 事件处理的重要性
前面的示例展示了Swing程序的基本形式,但缺少了一个重要部分:事件处理。由于JLabel不接受用户输入,因此不需要事件处理,但其他Swing组件会响应用户输入,需要处理这些交互产生的事件。
6.2 事件处理机制
Swing使用的事件处理机制与AWT相同,称为委托事件模型(delegation event model)。许多情况下,Swing使用与AWT相同的事件,这些事件封装在java.awt.event包中,特定于Swing的事件存储在javax.swing.event包中。
6.3 示例代码
// Handle an event in a Swing program.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class EventDemo {
JLabel jlab;
EventDemo() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("An Event Example");
// Specify FlowLayout for the layout manager.
jfrm.setLayout(new FlowLayout());
// Give the frame an initial size.
jfrm.setSize(220, 90);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Make two buttons.
JButton jbtnAlpha = new JButton("Alpha");
JButton jbtnBeta = new JButton("Beta");
// Add action listener for Alpha.
jbtnAlpha.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
jlab.setText("Alpha was pressed.");
}
});
// Add action listener for Beta.
jbtnBeta.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
jlab.setText("Beta was pressed.");
}
});
// Add the buttons to the content pane.
jfrm.add(jbtnAlpha);
jfrm.add(jbtnBeta);
// Create a text-based label.
jlab = new JLabel("Press a button.");
// Add the label to the content pane.
jfrm.add(jlab);
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new EventDemo();
}
});
}
}
6.4 代码解释
-
导入必要的包
:导入
java.awt、java.awt.event和javax.swing包。 -
创建JFrame容器
:使用
JFrame jfrm = new JFrame("An Event Example");创建一个带有标题的窗口。 -
设置布局管理器
:使用
jfrm.setLayout(new FlowLayout());将内容面板的布局管理器设置为FlowLayout。 -
创建按钮
:使用
JButton jbtnAlpha = new JButton("Alpha");和JButton jbtnBeta = new JButton("Beta");创建两个按钮。 -
添加动作监听器
:使用
jbtnAlpha.addActionListener和jbtnBeta.addActionListener为按钮添加动作监听器,当按钮被按下时,会调用actionPerformed方法更新标签的文本。 -
添加按钮和标签到内容面板
:使用
jfrm.add方法将按钮和标签添加到内容面板中。 -
显示窗口
:使用
jfrm.setVisible(true);使窗口可见。 -
在事件调度线程中创建窗口
:在
main方法中,使用SwingUtilities.invokeLater方法在事件调度线程中创建EventDemo对象。
6.5 事件处理流程
graph LR
A[用户操作按钮] --> B[按钮生成ActionEvent]
B --> C[调用ActionListener的actionPerformed方法]
C --> D[更新标签文本]
通过以上内容,我们对Java Swing编程的基本原理、组件、容器、简单应用程序和事件处理有了初步的了解。在实际开发中,可以根据需求进一步扩展和优化这些知识。
7. 深入理解组件与容器的使用
7.1 组件的布局管理
在Swing中,组件的布局管理非常重要,不同的布局管理器会影响组件在容器中的排列方式。常见的布局管理器如下:
| 布局管理器 | 说明 |
| ---- | ---- |
| BorderLayout | 分为东、西、南、北、中五个区域,组件可以放置在这些区域中。默认情况下,添加到容器的组件会被放置在中心区域。 |
| FlowLayout | 组件按照添加的顺序从左到右、从上到下排列,当一行放不下时会自动换行。 |
| GridLayout | 将容器划分为指定的行和列的网格,每个网格中放置一个组件。 |
| GridBagLayout | 功能强大但复杂的布局管理器,可以灵活地控制组件的大小和位置。 |
| BoxLayout | 可以将组件按水平或垂直方向排列。 |
例如,在前面的
EventDemo
程序中,使用了
FlowLayout
布局管理器:
jfrm.setLayout(new FlowLayout());
7.2 容器的嵌套使用
容器可以嵌套使用,形成复杂的界面布局。例如,可以在一个
JPanel
中添加多个组件,然后将这个
JPanel
添加到
JFrame
中。以下是一个简单的示例:
import javax.swing.*;
import java.awt.*;
public class NestedContainerDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame jfrm = new JFrame("Nested Container Example");
jfrm.setSize(300, 200);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 创建一个JPanel
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
// 在JPanel中添加组件
JLabel label = new JLabel("This is a label");
JButton button = new JButton("Click me");
panel.add(label);
panel.add(button);
// 将JPanel添加到JFrame中
jfrm.add(panel);
jfrm.setVisible(true);
});
}
}
在这个示例中,首先创建了一个
JPanel
,并设置其布局管理器为
FlowLayout
,然后在
JPanel
中添加了一个标签和一个按钮,最后将
JPanel
添加到
JFrame
中。
7.3 顶级容器的使用注意事项
顶级容器(如
JFrame
、
JApplet
、
JWindow
和
JDialog
)在使用时需要注意以下几点:
-
标题设置
:可以在创建顶级容器时通过构造函数设置标题,如
JFrame jfrm = new JFrame("My Frame");
。
-
大小设置
:使用
setSize
方法设置容器的初始大小,如
jfrm.setSize(300, 200);
。
-
关闭操作
:使用
setDefaultCloseOperation
方法设置关闭容器时的操作,常见的有
JFrame.EXIT_ON_CLOSE
(关闭窗口时退出程序)、
JFrame.DISPOSE_ON_CLOSE
(关闭窗口时释放资源)等。
-
可见性设置
:使用
setVisible
方法使容器可见,如
jfrm.setVisible(true);
。
8. 高级组件的使用
8.1 JTable的使用
JTable
用于显示和编辑表格数据。以下是一个简单的
JTable
使用示例:
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
public class JTableDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame jfrm = new JFrame("JTable Example");
jfrm.setSize(400, 300);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 定义表格的列名
String[] columnNames = {"Name", "Age", "City"};
// 定义表格的数据
Object[][] data = {
{"John", 25, "New York"},
{"Jane", 30, "Los Angeles"},
{"Bob", 35, "Chicago"}
};
// 创建表格模型
DefaultTableModel model = new DefaultTableModel(data, columnNames);
// 创建JTable
JTable table = new JTable(model);
// 将JTable添加到滚动面板中
JScrollPane scrollPane = new JScrollPane(table);
jfrm.add(scrollPane);
jfrm.setVisible(true);
});
}
}
在这个示例中,首先定义了表格的列名和数据,然后创建了一个
DefaultTableModel
,将列名和数据传递给它。接着创建了一个
JTable
,并将
DefaultTableModel
设置给它。最后将
JTable
添加到
JScrollPane
中,以便在表格数据较多时可以滚动查看。
8.2 JTree的使用
JTree
用于显示树形结构的数据。以下是一个简单的
JTree
使用示例:
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;
public class JTreeDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame jfrm = new JFrame("JTree Example");
jfrm.setSize(300, 200);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 创建根节点
DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
// 创建子节点
DefaultMutableTreeNode child1 = new DefaultMutableTreeNode("Child 1");
DefaultMutableTreeNode child2 = new DefaultMutableTreeNode("Child 2");
// 将子节点添加到根节点
root.add(child1);
root.add(child2);
// 创建JTree
JTree tree = new JTree(root);
// 将JTree添加到滚动面板中
JScrollPane scrollPane = new JScrollPane(tree);
jfrm.add(scrollPane);
jfrm.setVisible(true);
});
}
}
在这个示例中,首先创建了一个根节点
root
,然后创建了两个子节点
child1
和
child2
,并将它们添加到根节点中。接着创建了一个
JTree
,将根节点传递给它。最后将
JTree
添加到
JScrollPane
中。
9. 外观与感觉的定制
9.1 更改默认外观与感觉
可以在程序中更改Swing组件的外观与感觉。以下是一个更改外观与感觉为Nimbus的示例:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class LookAndFeelDemo {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
// 设置外观与感觉为Nimbus
UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) {
e.printStackTrace();
}
JFrame jfrm = new JFrame("Look and Feel Example");
jfrm.setSize(200, 150);
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Click me");
jfrm.add(button);
jfrm.setVisible(true);
});
}
}
在这个示例中,使用
UIManager.setLookAndFeel
方法将外观与感觉设置为Nimbus。
9.2 自定义外观与感觉
如果需要自定义外观与感觉,可以创建自己的
LookAndFeel
类。这需要继承
javax.swing.plaf.metal.MetalLookAndFeel
或其他合适的
LookAndFeel
类,并覆盖相应的方法。这个过程比较复杂,需要对Swing的内部机制有深入的了解。
10. 总结
通过以上内容,我们全面了解了Java Swing编程的各个方面,包括可插拔外观与感觉、MVC架构、组件与容器的使用、简单应用程序的编写、事件处理、高级组件的使用以及外观与感觉的定制等。在实际开发中,可以根据具体需求选择合适的组件和布局管理器,灵活运用事件处理机制,创建出功能丰富、界面美观的GUI应用程序。同时,要注意在事件调度线程中创建和更新Swing组件,以避免潜在的线程安全问题。
希望这些内容能帮助你更好地掌握Java Swing编程,开启精彩的GUI开发之旅!
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



