文章目录
- Swing图形用户界面编程
- Java图形用户界面技术
- Swing技术基础
- Swing类层次结构
- Swing程序结构
- 创建JFrame方式
- 继承JFrame方式
- 事件处理模型
- 采用内部类处理事件
- 采用Lambda表达式处理事件
- 使用适配器
- 布局管理
- FlowLayout布局
- BorderLayout布局
- GridLayout布局
- 不使用布局管理器
- Swing组件
- 标签和按钮
- 文本输入组件
- 复选框和单选按钮
- 下拉列表
- 列表
- 分隔面板
- 表格
- 案例:图书库存
Swing图形用户界面编程
图形用户界面(Graphical User Interface,简称 GUI)编程对于某种语言来说非常重要。Java的应用主要方向是基于Web浏览器的应用,用户界面主要是HTML、CSS和JavaScript等基于Web的技术,这些介绍要到Java EE阶段才能学习到。
而本章介绍的Java图形用户界面技术是基于Java SE的Swing,事实上它们在实际应用中使用不多,因此本章的内容只做了解。
(那问什么还要学它,因为入门小项目要用啊…)
Java图形用户界面技术
Java图形用户界面技术主要有:AWT、Applet、Swing和JavaFX。
- AWT
AWT(Abstract Window Toolkit)是抽象窗口工具包,AWT是Java 程序提供的建立图形用户界面最基础的工具集。AWT支持图形用户界面编程的功能包括:用户界面组件(控件)、事件处理模型、图形图像处理(形状和颜色)、字体、布局管理器和本地平台的剪贴板来进行剪切和粘贴等。AWT是Applet和Swing技术的基础。
AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段AWT程序在不同的操作系统平台下运行所看到的样式是不同的。(在Windows下运行,显示的窗口是Windows风格的窗口;在UNIX下运行时,显示的是UNIX风格的窗口。) - Applet
Applet称为Java小应用程序,Applet基础是AWT,但它主要嵌入到HTML代码中,由浏览器加载和运行,由于存在安全隐患和运行速度慢等问题,已经很少使用了。 - Swing
Swing是Java主要的图形用户界面技术,Swing提供跨平台的界面风格,用户可以自定义Swing的界面风格。Swing提供了比AWT更完整的组件,引入了许多新的特性。Swing API是围绕着实现AWT各个部分的API构筑的。Swing是由100%纯Java实现的,Swing组件没有本地代码,不依赖操作系统的支持,这是它与AWT组件的最大区别。(重点介绍) - JavaFX
JavaFX是开发丰富互联网应用程序(Rich Internet Application,缩写RIA)的图形用户界面技术,JavaFX期望能够在桌面应用的开发领域与Adobe公司的AIR、微软公司的Silverlight相竞争。传统的互联网应用程序基于Web,客户端是浏览器。而丰富互联网应用程序试图打造自己的客户端,替代浏览器。
Swing技术基础
AWT是Swing的基础,Swing事件处理和布局管理都是依赖于AWT,AWT内容来自java.awt包,Swing内容来自javax.swing包。AWT和Swing作为图形用户界面技术包括了4个主要的概念:组件 (Component)、容器(Container)、事件处理和布局管理器(LayoutManager)。
Swing类层次结构
容器和组件构成了Swing的主要内容。
Swing容器类主要有:JWindow、JFrame和JDialog,其他的不带“J”开头都是AWT提供的类,在Swing中大部分类都是以“J”开头。
Swing容器类层次结构:
Swing组件类层次结构,Swing所有组件继承自JComponent,JComponent又间接继承自AWT的java.awt.Component类。
Swing组件类层次结构:
Swing程序结构
图形用户界面主要是由窗口以及窗口中的组件构成的,编写Swing程序主要就是创建窗口和添加组件过程。Swing中的窗口主要是使用JFrame,很少使用JWindow。JFrame有标题、边框、菜单、大小和窗口管理按钮等窗口要素,而JWindow没有标题栏和窗口管理按钮。
构建Swing程序主要有两种方式:创建JFrame或继承JFrame。
示例:窗口标题是MyFrame,窗口中有显示字符串“Hello Swing!”。
创建JFrame方式
直接实例化JFrame对象,然后设置JFrame属性,添加窗口所需要的组件。
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class SwingDemo1 {
public static void main(String[] args) {
//创建窗口对象
JFrame frame = new JFrame("MyFrame");
// 创建标签
JLabel label = new JLabel("Hello Swing!");
// 获得窗口的内容面板
Container contentPane = frame.getContentPane();
// 添加标签到内容面板
contentPane.add(label);
// 设置窗口大小
frame.setSize(300, 300);
// 设置窗口可见
frame.setVisible(true);
}
}
第9行使用JFrame的JFrame(String title)构造方法创建JFrame对象,title是设置创建的标题。默认情况下JFrame是没有大小且不可见的,因此创建JFrame对象后还需要设置大小和可见
设置JFrame窗口大小和可见这两条语句,应该在添加完成所有组件之后调用。否则在多个组件情况下,会导致有些组件没有显示。
创建好窗口后,就需要将其中的组件添加进来,第11行是创建标签对象,构造方法中字符串参数是标签要显示的文本。创建好组件之后需要把它添加到窗口的内容面板上,第15行调用容器的add()方法将组件添加到窗口上。
在Swing中添加到JFrame上的所有可见组件,除菜单栏外,全部添加到内容面板上,不要直接添加到JFrame上,这是Swing绘制系统所要求的。内容面板是JFrame中包含的一个子容器。
几乎所有的图形用户界面技术,在构建界面时都采用层级结构(树形结构),根是顶级容器(只能包含其他容器的容器),子容器有内容面板和菜单栏,然后其他的组件添加到内容面板容器中。所有的组件都有add()方法,通过调用add()方法将其他组件添加到容器中,作为当前容器的子组件。
继承JFrame方式
编写一个继承JFrame的子类,在构造方法中初始化窗口,添加窗口所需要的组件。
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;
class MyFrame extends JFrame {
public MyFrame(String title) {
super(title);
// 创建标签
JLabel label = new JLabel("Hello Swing!");
// 获得窗口的内容面板
Container contentPane = getContentPane();
// 添加标签到内容面板
contentPane.add(label);
// 设置窗口大小
setSize(300, 300);
// 设置窗口可见
setVisible(true);
}
}
public class SwingDemo2{
public static void main(String[] args) {
//创建窗口对象
new MyFrame("MyFrame");
}
}
创建JFrame方式适合于小项目,代码量少、窗口不多、组件少的情况。继承JFrame 方式,适合于大项目,可以针对不同界面自定义一个Frame类,属性可以在构造方法中进行设置;缺点是有很多类文件需要有效地管理。
事件处理模型
图形界面的组件要响应用户操作,就必须添加事件处理机制。Swing采用AWT的事件处理模型进行事件处理。在事件处理的过程中涉及三个要素:
- 事件:是用户对界面的操作,在Java中事件被封装称为事件类java.awt.AWTEvent及其子类,例如按钮单击事件类是java.awt.event.ActionEvent。
- 事件源:是事件发生的场所,就是各个组件,例如按钮单击事件的事件源是按钮(Button)。
- 事件处理者:是事件处理程序,在Java中事件处理者是实现特定接口的事件对象。
在事件处理模型中最重要的是事件处理者,它根据事件(XXXEvent)的不同会实现不同的接口,这些接口命名为XXXListener,所以事件处理者也称为事件监听器。最后事件源通过addXXXListener()方法添加事件监听,监听XXXEvent事件。
各种事件和对应的监听器接口:
事件类型 | 相应监听器接口 | 监听器接口中的方法 |
Action | ActionListener | actionPerformed(ActionEvent) |
Item | ItemListener | itemStateChanged(ItemEvent) |
Mouse | MouseListener | mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mouseClicked(MouseEvent) |
MouseMotion | MouseMotionListener | mouseDragged(MouseEvent) mouseMoved(MouseEvent) |
Key | KeyListener | KeyPressed(KeyEvent) KeyReleased(KeyEvent) KeyTyped(KeyEvent) |
Focus | FocusListener | focusGained(FocusEvent) focusLost(FocusEvent) |
Adjustment | AdjustmentListener | adjustmentValueChanged(AdjustmentEvent) |
Component | ComponentListener | componentMoved(ComponentEvent) componentHidden(ComponentEvent) componentResized(ComponentEvent) componentShown(ComponentEvent) |
Window | WindowListener | windowClosing(WindowEvent) windowOpened(WindowEvent) windowIconified(WindowEvent) windowJDeiconified(WindowEvent) windowClosed(WindowEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) |
Container | ContainerListener | componentAdded(ContainerEvent) componentAdded(ContainerEvent) |
Text | TextListener | textValueChanged(TextEvent) |
事件处理者可以实现XXXListener接口任何形式,即:外部类、内部类、匿名内部类和Lambda表达式;如果XXXListener接口只有一个抽象方法,事件处理者还可以是Lambda表达式。为了访问窗口中的组件方便,往往使用内部类、匿名内部类和Lambda表达式。
采用内部类处理事件
示例:界面中有两个按钮和一个标签,当单击Button1或Button2时会改变标签显示的内容。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
class MyFrame extends JFrame {
// 声明标签
JLabel label;
public MyFrame(String title) {
super(title);
// 创建标签
label = new JLabel("Label");
// 添加标签到内容面板
getContentPane().add(label, BorderLayout.NORTH);
// 创建Button1
JButton button1 = new JButton("Button1");
// 添加Button1到内容面板
getContentPane().add(button1, BorderLayout.CENTER);
// 创建Button2
JButton button2 = new JButton("Button2");
// 添加Button2到内容面板
getContentPane().add(button2, BorderLayout.SOUTH);
// 设置窗口大小
setSize(350, 120);
// 设置窗口可见
setVisible(true);
// 注册事件监听器,监听Button2单击事件
button2.addActionListener(new ActionEventHandler());
// 注册事件监听器,监听Button1单击事件
button1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {
label.setText("Hello Swing!");
}
});
}
// Button2事件处理者
class ActionEventHandler implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("Hello World!");
}
}
}
第18行通过add(button1, BorderLayout.NORTH)方法将标签添加到内容面板,这个add()方法与前面介绍的有所不同,它的第二个参数是指定组件的位置。
在事件处理模型中,内部类实现的模型,内部类会定义为成员内部类,因此不能访问其他方法中的局部变量组件,只能访问成员变量组件,所以代码第11行将标签组件声明为成员变量, 否则ActionEventHandler内部类无法访问该组件。而匿名内部类既可以访问所在方法的局部变量组件,也可以访问成员变量组件。
采用Lambda表达式处理事件
如果一个事件监听器接口只有一个抽象方法,则可以使用Lambda表达式实现事件处理,这些接口主要有:ActionListener、AdjustmentListener、ItemListener、MouseWheelListener、TextListener和 WindowStateListener等。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class MyFrame extends JFrame implements ActionListener {
// 声明标签
JLabel label;
public MyFrame(String title) {
super(title);
// 创建标签
label = new JLabel("Label");
// 添加标签到内容面板
getContentPane().add(label, BorderLayout.NORTH);
// 创建Button1
JButton button1 = new JButton("Button1");
// 添加Button1到内容面板
getContentPane().add(button1, BorderLayout.CENTER);
// 创建Button2
JButton button2 = new JButton("Button2");
// 添加Button2到内容面板
getContentPane().add(button2, BorderLayout.SOUTH);
// 设置窗口大小
setSize(350, 120);
// 设置窗口可见
setVisible(true);
// 注册事件监听器,监听Button2单击事件
button2.addActionListener(this);
// 注册事件监听器,监听Button1单击事件
button1.addActionListener((event) -> {
label.setText("Hello World!");
});
}
@Override
public void actionPerformed(ActionEvent event) {
label.setText("Hello Swing!");
}
}
代码第34行采用Lambda表达式实现的事件监听器,可见代码非常简单。另外,当前窗口本身也可以是事件处理者,代码第8行声明窗口实现ActionListener接口,代码第40行是实现抽象方法,那么注册事件监听器参数就是this了,见代码第32行。
使用适配器
事件监听器都是接口,在Java接口中定义的抽象方法必须全部实现(哪怕对某些方法并不关心,也要给一对空的大括号表示实现)。例如WindowListener是窗口事件(WindowEvent)监听器接口,为了在窗口中接收到窗口事件,需要在窗口中注册WindowListener事件监听器。
this.addWindowListener(new WindowListener() {
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
// 退出系统
System.exit(0);
}
@Override
public void windowDeactivated(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowOpened(WindowEvent e) {
}
});
Java提供了一些与监听器相配套的适配器。监听器是接口,命名采用XXXListener,而适配器是类,命名采用XXX Adapter。在使用时通过继承事件所对应的适配器类,覆盖所需要的方法,无关方法不用实现。
this.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e) {
// 退出系统
System.exit(0);
}
});
由于Java的单一继承机制,当需要多种监听器或此类已有父类时,就无法采用事件适配器了。
并非所有的监听器接口都有对应的适配器类,一般定义了多个方法的监听器接口,例如 WindowListener有多个方法对应多种不同的窗口事件时,才需要配套的适配器,主要的适配器如下:
ComponentAdapter 组件适配器
ContainerAdapter 容器适配器
FocusAdapter 焦点适配器
KeyAdapter 键盘适配器
MouseAdapter 鼠标适配器
MouseMotionAdapter 鼠标运动适配器
WindowAdapter 窗口适配器
布局管理
Java为了实现图形用户界面的跨平台,并实现动态布局等效果,Java将容器内的所有组件布局交给布局管理器管理。布局管理器负责组件的排列顺序、大小、位置,当窗口移动或调整大小后组件如何变化等。
Java SE提供了7种布局管理器:FlowLayout、BorderLayout、GridLayout、BoxLayout、 CardLayout、SpringLayout和GridBagLayout,其中最基础的是FlowLayout、BorderLayout和GridLayout 布局管理器。
FlowLayout布局
FlowLayout布局摆放组件的规律是:从上到下、从左到右进行摆放,如果容器足够宽,第一个组件先添加到容器中第一行的最左边,后续的组件依次添加到上一个组件的右边,如果当前行已摆放不下该组件,则摆放到下一行的最左边。
FlowLayout构造方法:
FlowLayout(int align, int hgap, int vgap) 创建一个FlowLayout对象,它具有指定的对齐方式以及指定的水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙, 单位是像素。
FlowLayout(int align) 创建一个FlowLayout对象,指定的对齐方式,默认的水平和垂直间隙是5个单位。
FlowLayout() 创建一个FlowLayout对象,它是居中对齐的,默认的水平和垂直间隙是5个单位。
上述参数align是对齐方式,它是通过FlowLayout的常量指定的,这些常量说明如下:
FlowLayout.CENTER 指示每一行组件都应该是居中的。
FlowLayout.LEADING 指示每一行组件都应该与容器方向的开始边对齐,例如,对于从左到右的方向,则与左边对齐。
FlowLayout.LEFT 指示每一行组件都应该是左对齐的。
FlowLayout.RIGHT 指示每一行组件都应该是右对齐的。
FlowLayout.TRAILING 指示每行组件都应该与容器方向的结束边对齐,例如,对于从左到右的方向,则与右边对齐。
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class MyFrame extends JFrame {
// 声明标签
JLabel label;
public MyFrame(String title) {
super(title);
setLayout(new FlowLayout(FlowLayout.LEFT, 20, 20));
// 创建标签
label = new JLabel("Label");
// 添加标签到内容面板
getContentPane().add(label);
// 创建Button1
JButton button1 = new JButton("Button1");
// 添加Button1到内容面板
getContentPane().add(button1);
// 创建Button2
JButton button2 = new JButton("Button2");
// 添加Button2到内容面板
getContentPane().add(button2);
// 设置窗口大小
setSize(350, 120);
// 设置窗口可见
setVisible(true);
// 注册事件监听器,监听Button2单击事件
button2.addActionListener((event) -> {
label.setText("Hello Swing!");
});
// 注册事件监听器,监听Button1单击事件
button1.addActionListener((event) -> {
label.setText("Hello World!");
});
}
}
第13行是设置当前窗口的布局是FlowLayout布局,采用FlowLayout(int align, int hgap, int vgap) 构造方法。一旦设置了FlowLayout布局,就可以通过add(Component comp)方法添加组件到窗口的内容面板。
采用FlowLayout布局如果水平空间比较小,组件会垂直摆放(拖曳窗口的边缘使窗口变窄,最后一个组件换行)。
BorderLayout布局
BorderLayout布局是窗口的默认布局管理器。
BorderLayout是JWindow、JFrame和JDialog的默认布局管理器。BorderLayout布局管理器把容器分成5个区域:North、South、East、West和Center,每个区域只能放置一个组件。(North与South占整行,其余横向中间部分分为West、Center、East)
BorderLayout构造方法:
BorderLayout(int hgap, int vgap) 创建一个BorderLayout对象,指定水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙,单位是像素。
BorderLayout() 创建一个BorderLayout对象,组件之间没有间隙。
BorderLayout布局有5个区域,为此BorderLayout中定义了5个约束常量,说明如下:
BorderLayout.CENTER 中间区域的布局约束(容器中央)
BorderLayout.EAST 东区域的布局约束(容器右边)
BorderLayout.NORTH 北区域的布局约束(容器顶部)
BorderLayout.SOUTH 南区域的布局约束(容器底部)
BorderLayout.WEST 西区域的布局约束(容器左边)
import java.awt.BorderLayout;
import java.awt.Button;
import javax.swing.JFrame;
public class MyFrame extends JFrame {
public MyFrame(String title) {
super(title);
// 设置BorderLayout布局
setLayout(new BorderLayout(10, 10));
// 添加按钮到容器的North区域
getContentPane().add(new Button("北"), BorderLayout.NORTH);
// 添加按钮到容器的South区域
getContentPane().add(new Button("南"), BorderLayout.SOUTH);
// 添加按钮到容器的East区域
getContentPane().add(new Button("东"), BorderLayout.EAST);
// 添加按钮到容器的West区域
getContentPane().add(new Button("西"), BorderLayout.WEST);
// 添加按钮到容器的Center区域
getContentPane().add(new Button("中"), BorderLayout.CENTER);
setSize(300, 300);
setVisible(true);
}
}
添加方法是add(Component comp, Object constraints),第二个参数constraints指定约束。
当使用BorderLayout时,如果容器的大小发生变化,其变化规律为:组件的相对位置不变,大小发生变化。如果容器变高或矮,则North和South不变,West、Center和East变高或矮;如果容器变宽或窄,West和East区域不变,North、Center和South变宽或窄。
在5个区域中不一定都放置了组件,如果某个区域缺少组件,界面布局会有比较大的影响。
GridLayout布局
GridLayout布局以网格形式对组件进行摆放,容器被分成大小相等的矩形,一个矩形中放置一个组件。
GridLayout构造方法:
GridLayout() 创建具有默认值的GridLayout对象,即每个组件占据一行一列。
GridLayout(int rows, int cols) 创建具有指定行数和列数的GridLayout对象。
GridLayout(int rows, int cols, int hgap, int vgap) 创建具有指定行数和列数的GridLayout对象,并指定水平和垂直间隙。
import java.awt.Button;
import java.awt.GridLayout;
import javax.swing.JFrame;
public class MyFrame extends JFrame {
public MyFrame(String title) {
super(title);
// 设置3行3列的GridLayout布局管理器
setLayout(new GridLayout(3, 3));
// 添加按钮到第一行的第一格
getContentPane().add(new Button("1"));
// 添加按钮到第一行的第二格
getContentPane().add(new Button("2"));
// 添加按钮到第一行的第三格
getContentPane().add(new Button("3"));
// 添加按钮到第二行的第一格
getContentPane().add(new Button("4"));
// 添加按钮到第二行的第二格
getContentPane().add(new Button("5"));
// 添加按钮到第二行的第三格
getContentPane().add(new Button("6"));
// 添加按钮到第三行的第一格
getContentPane().add(new Button("7"));
// 添加按钮到第三行的第二格
getContentPane().add(new Button("8"));
// 添加按钮到第三行的第三格
getContentPane().add(new Button("9"));
setSize(400, 400);
setVisible(true);
}
}
第10行是设置当前窗口布局采用3行3列的GridLayout布局,它有9个区域,分别从左到右,从上到下摆放。
GridLayout布局将容器分成几个区域,也会出现某个区域缺少组件情况,GridLayout布局会根据行列划分的不同,会平均占据容器的空间,实际情况比较复杂。
不使用布局管理器
如果要开发的图形用户界面应用不考虑跨平台,不考虑动态布局,窗口大小不变的,那么布局管理器就失去使用的意义。容器也可以不设置布局管理器,那么此时的布局是由开发人员自己管理的。
组件有三个与布局有关的方法setLocation()、setSize()和setBounds(),在设置了布局管理的容器中组件的这几个方法不起作用,不设置布局管理时它们才起作用。
void setLocation(int x, int y) 方法是设置组件的位置。
void setSize(int width, int height) 方法是设置组件的大小。
void setBounds(int x, int y, int width, int height) 方法是设置组件的大小和位置。
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
public class MyFrame extends JFrame {
public MyFrame(String title) {
super(title);
//设置窗口大小不变的
setResizable(false);
// 不设置布局管理器
getContentPane().setLayout(null);
// 创建标签
JLabel label = new JLabel("Label");
// 设置标签的位置和大小
label.setBounds(89, 13, 100, 30);
// 设置标签文本水平居中
label.setHorizontalAlignment(SwingConstants.CENTER);
// 添加标签到内容面板
getContentPane().add(label);
// 创建Button1
JButton button1 = new JButton("Button1");
// 设置Button1的位置和大小
button1.setBounds(89, 59, 100, 30);
// 添加Button1到内容面板
getContentPane().add(button1);
// 创建Button2
JButton button2 = new JButton("Button2");
// 设置Button2的位置
button2.setLocation(89, 102);
// 设置Button2的大小
button2.setSize(100, 30);
// 添加Button2到内容面板
getContentPane().add(button2);
// 设置窗口大小
setSize(300, 200);
// 设置窗口可见
setVisible(true);
// 注册事件监听器,监听Button2单击事件
button2.addActionListener((event) -> {
label.setText("Hello Swing!");
});
// 注册事件监听器,监听Button1单击事件
button1.addActionListener((event) -> {
label.setText("Hello World!");
});
}
}
第11行是设置不能调整窗口大小,没有设置布局管理器后,容器中的组件都绝对布局,容器大小如果变化,那么其中的组件大小和位置都不会变化,将窗口拉大后,组件还是在原来的位置。
(我要放弃Eclipse了…IDEA我来了)
Swing组件
Swing所有组件都继承自JComponent,主要有文本处理、按钮、标签、列表、面板、组合框、滚动条、滚动面板、菜单、表格和树等组件。
标签和按钮
Swing中标签类是JLabel,它不仅可以显示文本还可以显示图标。
JLabel构造方法:
JLabel() 创建一个无图标无标题标签对象。
JLabel(Icon image) 创建一个具有图标的标签对象。
JLabel(Icon image, int horizontalAlignment) 通过指定图标和水平对齐方式创建标签对象。
JLabel(String text) 创建一个标签对象,并指定显示的文本。
JLabel(String text, Icon icon, int horizontalAlignment) 通过指定显示的文本、图标和水平对齐方式创建标签对象。
JLabel(String text, int horizontalAlignment) 通过指定显示的文本和水平对齐方式创建标签对象。
上述构造方法horizontalAlignment参数是水平对齐方式,它的取值是SwingConstants中定义的以下常量之一:LEFT、CENTER、RIGHT、LEADING 或 TRAILING。
Swing中的按钮类是JButton,JButton不仅可以显示文本还可以显示图标。
JButton构造方法:
JButton() 创建不带文本或图标的按钮对象。
JButton(Icon icon) 创建一个带图标的按钮对象。
JButton(String text):创建一个带文本的按钮对象。
JButton(String text, Icon icon) 创建一个带初始文本和图标的按钮对象。
示例:界面中上面图标是标签, 下面两个图标是按钮,当单击按钮时标签可以切换图标。
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingConstants;
public class MyFrame extends JFrame {
// 用于标签切换的图标
private static Icon images[] = {
new ImageIcon("./icon/0.png"),
new ImageIcon("./icon/1.png"),
new ImageIcon("./icon/2.png"),
new ImageIcon("./icon/3.png"),
new ImageIcon("./icon/4.png"),
new ImageIcon("./icon/5.png") };
// 当前页索引
private static int currentPage = 0;
public MyFrame(String title) {
super(title);
// 设置窗口大小不变
setResizable(false);
// 不设置布局管理器
getContentPane().setLayout(null);
// 创建标签
JLabel label = new JLabel(images[0]);
// 设置标签的位置和大小
label.setBounds(94, 27, 100, 50);
// 设置标签文本水平居中
label.setHorizontalAlignment(SwingConstants.CENTER);
// 添加标签到内容面板
getContentPane().add(label);
// 创建向后翻页按钮
JButton backButton = new JButton(new ImageIcon("./icon/ic_menu_back.png"));
// 设置按钮的位置和大小
backButton.setBounds(77, 90, 47, 30);
// 添加按钮到内容面板
getContentPane().add(backButton);
// 创建向前翻页按钮
JButton forwardButton =
new JButton(new ImageIcon("./icon/ic_menu_forward.png"));
// 设置按钮的位置和大小
forwardButton.setBounds(179, 90, 47, 30);
// 添加按钮到内容面板
getContentPane().add(forwardButton);
// 设置窗口大小
setSize(300, 200);
// 设置窗口可见
setVisible(true);
// 注册事件监听器,监听向后翻页按钮单击事件
backButton.addActionListener((event) -> {
if (currentPage < images.length - 1) {
currentPage++;
}
label.setIcon(images[currentPage]);
});
// 注册事件监听器,监听向前翻页按钮单击事件
forwardButton.addActionListener((event) -> {
if (currentPage > 0) {
currentPage--;
}
label.setIcon(images[currentPage]);
});
}
}
第11行定义ImageIcon数组,用于标签切换图标,注意Icon是接口,ImageIcon是实现Icon接口。代码第20行currentPage变量记录了当前页索引,前后翻页按钮会改变前页索引。
文本输入组件
文本输入组件主要有:文本框(JTextField)、密码框(JPasswordField)和文本区(JTextArea)。文本框和密码框都只能输入和显示单行文本。当按下Enter键时,可以触发ActionEvent事件。而文本区可以输入和显示多行多列文本。
文本框(JTextField)构造方法:
JTextField() 创建一个空的文本框对象。
JTextField(int columns) 指定列数,创建一个空的文本框对象,列数是文本框显示的宽度,列数主要用于FlowLayout布局。
JTextField(String text) 创建文本框对象,并指定初始化文本。
JTextField(String text, int columns) 创建文本框对象,并指定初始化文本和列数。
JPasswordField继承自JTextField,构造方法类似。
文本区(JTextArea)构造方法:
JTextArea() 创建一个空的文本区对象。
JTextArea(int rows, int columns) 创建文本区对象,并指定行数和列数。
JTextArea(String text) 创建文本区对象,并指定初始化文本。
JTextArea(String text, int rows, int columns) 创建文本区对象,并指定初始化文本、行数和列数。
示例:界面中有三个标签(TextField、Password、TextArea)。
可以采用布局嵌套,将TextField标签、Password标签、文本框和密码框都放到一个面板(panel1) 中;将TextArea和文本区放到一个面板(panel2)中。两个面板panel1和panel2放到内容视图中,内容视图采用BorderLayout布局,每个面板内部采用FlowLayout布局。
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class MyFrame extends JFrame {
private JTextField textField;
private JPasswordField passwordField;
public MyFrame(String title) {
super(title);
// 设置布局管理BorderLayout
getContentPane().setLayout(new BorderLayout());
// 创建一个面板panel1放置TextField和Password
JPanel panel1 = new JPanel();
// 将面板panel1添加到内容视图
getContentPane().add(panel1, BorderLayout.NORTH);
// 创建标签
JLabel lblTextFieldLabel = new JLabel("TextField:");
// 添加标签到面板panel1
panel1.add(lblTextFieldLabel);
// 创建文本框
textField = new JTextField(12); // 指定列数12
// 添加文本框到面板panel1
panel1.add(textField);
// 创建标签
JLabel lblPasswordLabel = new JLabel("Password:");
// 添加标签到面板panel1
panel1.add(lblPasswordLabel);
// 创建密码框
passwordField = new JPasswordField(12); // 指定列数12
// 添加密码框到面板panel1
panel1.add(passwordField);
// 创建一个面板panel2放置TextArea
JPanel panel2 = new JPanel();
getContentPane().add(panel2, BorderLayout.SOUTH);
// 创建标签
JLabel lblTextAreaLabel = new JLabel("TextArea:");
// 添加标签面板panel2
panel2.add(lblTextAreaLabel);
// 创建文本区
JTextArea textArea = new JTextArea(3, 20); // 指定行数3,列数20
// 添加文本区到面板panel2
panel2.add(textArea);
// 设置窗口大小
pack(); // 紧凑排列(将容器中所有组件刚好包裹),其作用相当于setSize()
// 设置窗口可见
setVisible(true);
textField.addActionListener((event)->{
textArea.setText("在文本框上按下Enter键");
});
}
}
第20、45行,面板(JPanel)是一种没有标题栏和边框的容器,经常用于嵌套布局。然后再将这两个面板,添加到内容视图中。
复选框和单选按钮
Swing中提供了用于多选和单选功能的组件。
多选组件是复选框(JCheckBox),有时也单独使用,能提供两种状态的开和关。
单选组件是单选按钮(JRadioButton),同一组的多个单选按钮应该具有互斥特性,又名收音机按钮(RadioButton),一个按钮按下时,其他按钮一定抬起。同一组多个单选按钮应该放到同一个ButtonGroup对象,ButtonGroup对象不属于容器,它会创建一个互斥作用范围。
JCheckBox构造方法:
JCheckBox() 创建一个没有文本、没有图标并且最初未被选定的复选框对象。
JCheckBox(Icon icon) 创建有一个图标、最初未被选定的复选框对象。
JCheckBox(Icon icon, boolean selected) 创建一个带图标的复选框对象,并指定其最初是否处于选定状态。
JCheckBox(String text) 创建一个带文本的、最初未被选定的复选框对象。
JCheckBox(String text, boolean selected) 创建一个带文本的复选框对象,并指定其最初是否处于选定状态。
JCheckBox(String text, Icon icon) 创建带有指定文本和图标的、最初未被选定的复选框对象。
JCheckBox(String text, Icon icon, boolean selected) 创建一个带文本和图标的复选框对象,并指定其最初是否处于选定状态。
JCheckBox和JRadioButton它们有着相同的父类JToggleButton,有着相同方法和类似的构造方法。
示例:界面中有一组复选框和一组单选按钮。
public class MyFrame extends JFrame implements ItemListener {
// 声明并创建RadioButton对象
private JRadioButton radioButton1 = new JRadioButton("男");
private JRadioButton radioButton2 = new JRadioButton("女");
public MyFrame(String title) {
super(title);
// 设置布局管理BorderLayout
getContentPane().setLayout(new BorderLayout());
// 创建一个面板panel1放置TextField和Password
JPanel panel1 = new JPanel();
FlowLayout flowLayout_1 = (FlowLayout) panel1.getLayout();
flowLayout_1.setAlignment(FlowLayout.LEFT);
// 将面板panel1添加到内容视图
getContentPane().add(panel1, BorderLayout.NORTH);
// 创建标签
JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
// 添加标签到面板panel1
panel1.add(lblTextFieldLabel);
JCheckBox checkBox1 = new JCheckBox("Java");
panel1.add(checkBox1);
JCheckBox checkBox2 = new JCheckBox("C++");
panel1.add(checkBox2);
JCheckBox checkBox3 = new JCheckBox("Objective-C");
// 注册checkBox3对ActionLEvent事件监听
checkBox3.addActionListener((event) -> {
// 打印checkBox3状态
System.out.println(checkBox3.isSelected());
});
panel1.add(checkBox3);
// 创建一个面板panel2放置TextArea
JPanel panel2 = new JPanel();
FlowLayout flowLayout = (FlowLayout) panel2.getLayout();
flowLayout.setAlignment(FlowLayout.LEFT);
getContentPane().add(panel2, BorderLayout.SOUTH);
// 创建标签
JLabel lblTextAreaLabel = new JLabel("选择性别:");
// 添加标签到面板panel2
panel2.add(lblTextAreaLabel);
// 创建ButtonGroup对象
ButtonGroup buttonGroup = new ButtonGroup();
// 添加RadioButton到ButtonGroup对象
buttonGroup.add(radioButton1);
buttonGroup.add(radioButton2);
// 添加RadioButton到面板panel2
panel2.add(radioButton1);
panel2.add(radioButton2);
//注册ItemEvent事件监听器
radioButton1.addItemListener(this);
radioButton2.addItemListener(this);
// 设置窗口大小
pack(); // 紧凑排列,其作用相当于setSize()
// 设置窗口可见
setVisible(true);
}
// 实现ItemListener接口方法
@Override
public void itemStateChanged(ItemEvent) {
if (e.getStateChange() == ItemEvent.SELECTED) {
JRadioButton button = (JRadioButton) e.getItem();
System.out.println(button.getText());
}
}
}
第4、5行创建了两个单选按钮对象,为了能让这两个单选按钮互斥,需要把它们添加到一个ButtonGroup对象(51行),并把它们添加进来。为了监听两单选按钮的选择状态,注册ItemEvent事件监听器(61、62行),为了一起处理两个单选按钮事件,它们需要使用同一个事件处理者,本例是this,这说明当前窗口是事件处理者,它实现了ItemListener接口,见代码第1行代码第73行实现了ItemListener接口的抽象方法。两个单选按钮使用同一个事件处理者,第74行是判断按钮是否被选中,如果选中通过e.getItem() 方法获得按钮引用,然后再通过getText()方法获得按钮的文本标签。
第25行是创建了一个复选框对象,并且应该把它添加到面板panel1中。复选框和单选按钮都属于按钮,也能响应ActionEvent事件,代码第33行是注册checkBox3对ActionLEvent事件监听。
下拉列表
Swing中提供了下拉列表(JComboBox)组件,每次只能选择其中的一项。
JComboBox构造方法:
JComboBox() 创建一个下拉列表对象。
JComboBox(Object [] items) 创建一个下拉列表对象,items设置下拉列表中选项。下拉列表中选项内容可以是任意类,而不再局限于String。
public class MyFrame extends JFrame {
// 声明下拉列表JComboBox
private JComboBox choice1;
private JComboBox choice2;
private String[] s1 = { "Java", "C++", "Objective-C" };
private String[] s2 = { "男", "女" };
public MyFrame(String title) {
super(title);
getContentPane().setLayout(new GridLayout(2, 2, 0, 0));
// 创建标签
JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
lblTextFieldLabel.setHorizontalAlignment(SwingConstants.RIGHT);
getContentPane().add(lblTextFieldLabel);
// 实例化JComboBox对象
choice1 = new JComboBox(s1);
// 注册Action事件监听器,采用Lambda表达式
choice1.addActionListener(e -> {
JComboBox cb = (JComboBox) e.getSource(); // 通过e事件参数获得事件源
// 获得选择的项目
String itemString = (String) cb.getSelectedItem();
System.out.println(itemString);
});
getContentPane().add(choice1);
// 创建标签
JLabel lblTextAreaLabel = new JLabel("选择性别:");
lblTextAreaLabel.setHorizontalAlignment(SwingConstants.RIGHT);
getContentPane().add(lblTextAreaLabel);
// 实例化JComboBox对象,采用Lambda表达式
choice2 = new JComboBox(s2);
// 注册项目选择事件侦听器
choice2.addItemListener(e -> {
// 项目选择
if (e.getStateChange() == ItemEvent.SELECTED) {
// 获得选择的项目
String itemString = (String) e.getItem(); // 从e事件参数中取出项目对象
System.out.println(itemString);
}
});
getContentPane().add(choice2);
// 设置窗口大小
setSize(400, 150);
// 设置窗口可见
setVisible(true);
}
}
下拉列表组件在进行事件处理时,可以注册两种事件监听器:ActionListener和ItemListener,这两个监听器都只有一 个抽象方法需要实现,因此可以采用Lambda表达式作为事件处理者。
列表
Swing中提供了列表(JList)组件,可以单选或多选。
JList构造方法:
JList() 创建一个列表对象。
JList(Object [] listData) 创建一个列表对象,listData设置列表中选项。列表中选项内容可以是任意类,而不再局限于String。
public class MyFrame extends JFrame {
private String[] s1 = { "Java", "C++", "Objective-C" };
public MyFrame(String title) {
super(title);
// 创建标签
JLabel lblTextFieldLabel = new JLabel("选择你喜欢的编程语言:");
getContentPane().add(lblTextFieldLabel, BorderLayout.NORTH);
// 列表组件JList
JList list1 = new JList(s1);
list1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 设置列表为单选
// 注册项目选择事件监听器,采用Lambda表达式
list1.addListSelectionListener(e -> {
if (e.getValueIsAdjusting() == false) { // false鼠标释放,true鼠标按下
// 获得选择的内容
String itemString = (String) list1.getSelectedValue();
System.out.println(itemString);
}
});
getContentPane().add(list1);
// 设置窗口大小
setSize(300, 200);
// 设置窗口可见
setVisible(true);
}
}
18行取出getSelectedValue()获得选中的项目值,如果是多选时可以通过getSelectedValues()获得选中的项目值。
分隔面板
Swing中提供了一种分隔面板(JSplitPane)组件,可以将屏幕分成左右或上下两部分。
JSplitPane构造方法:
JSplitPane(int newOrientation) 创建一个分隔面板,参数newOrientation指定布局方向,newOrientation取值是JSplitPane.HORIZONTAL_SPLIT(水平)或JSplitPane.VERTICAL_SPLIT(垂直)。
JSplitPane(int newOrientation, Component newLeftComponent, Component newRightComponent) 创建一个分隔面板,参数newOrientation指定布局方向,newLeftComponent指定左侧面板组件, newRightComponent指定右侧面板组件。
示例:界面分左右两部分,左边有列表组件, 选中列表项目时右边会显示相应的图片。
public class MyFrame extends JFrame {
private String[] data = { "bird1.gif", "bird2.gif", "bird3.gif", "bird4.gif", "bird5.gif", "bird6.gif" };
public MyFrame(String title) {
super(title);
// 右边面板
JPanel rightPane = new JPanel();
rightPane.setLayout(new BorderLayout(0, 0));
JLabel lblImage = new JLabel();
lblImage.setHorizontalAlignment(SwingConstants.CENTER);
rightPane.add(lblImage, BorderLayout.CENTER);
// 左边面板
JPanel leftPane = new JPanel();
leftPane.setLayout(new BorderLayout(0, 0));
JLabel lblTextFieldLabel = new JLabel("选择鸟儿:");
leftPane.add(lblTextFieldLabel, BorderLayout.NORTH);
// 列表组件JList
JList list1 = new JList(data);
list1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// 注册项目选择事件监听器,采用Lambda表达式
list1.addListSelectionListener(e -> {
if (e.getValueIsAdjusting() == false) {
// 获得选择的内容
String itemString = (String) list1.getSelectedValue();
// 获得图片的相对路径
String petImage = String.format("/images/%s", itemString);
// 创建图片ImageIcon对象
// MyFrame.class.getResource(petImage)获取资源图片的绝对路径
Icon icon = new ImageIcon(MyFrame.class.getResource(petImage));
lblImage.setIcon(icon);
}
});
leftPane.add(list1, BorderLayout.CENTER);
// 分隔面板
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPane, rightPane);
splitPane.setDividerLocation(100); // 设置分隔条位置
// 将分隔面板添加到内容面板中
getContentPane().add(splitPane, BorderLayout.CENTER);
// 设置窗口大小
setSize(300, 200);
// 设置窗口可见
setVisible(true);
}
}
(32行)资源文件是放在字节码文件夹中的文件,可通过XXX.class.getResource()方法获得它的运行时绝对路径。
表格
当有大量数据需要展示时,可以使用二维表格,有时也可以使用表格修改数据。表格是非常重要的组件。
Swing提供了表格组件JTable类,但是表格组件比较复杂,它的表现形式与数据是分离的,Swing的很多组件都是按照MVC设计模式进行设计的,JTable最有代表性。
按照MVC设计理念JTable属于视图,对应的模型是javax.swing.table.TableModel接口,根据自己的业务逻辑和数据实现TableModel接口实现类。TableModel接口要求实现所有抽象方法,使用起来比较麻烦,有时只是使用很简单的表格, 此时可以使用AbstractTableModel抽象类。实际开发时需要继承AbstractTableModel抽象类。
MVC是一种设计理念,将一个应用分为:模型(Model)、视图(View)和控制器(Controller),它将业务逻辑、数据、界面表示进行分离的方法组织代码,界面表示的变化不会影响到业务逻辑组件,不需要重新编写业务逻辑。
JTable构造方法:
JTable(TableModel dm) 通过模型创建表格,dm是模型对象,其中包含了表格要显示的数据。
JTable(Object[][] rowData, Object[] columnNames) 通过二维数组和指定列名,创建一个表格对象,rowData是表格中的数据,columnNames是列名。
JTable(int numRows, int numColumns) 指定行和列数创建一个空的表格对象。
先介绍通过二维数组和列名实现表格。这种方式创建表格不需要模型,实现起来比较简单。但是表格只能接受二维数组作为数据。
public class MyFrameTable extends JFrame {
// 获得当前屏幕的宽高
private double screenWidth = Toolkit.getDefaultToolkit().
getScreenSize().getWidth();
private double screenHeight = Toolkit.getDefaultToolkit().
getScreenSize().getHeight();
private JTable table;
public MyFrameTable(String title) {
super(title);
table = new JTable(rowData, columnNames);
// 设置表中内容字体
table.setFont(new Font("微软雅黑", Font.PLAIN, 16));
// 设置表列标题字体
table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 16));
// 设置表行高
table.setRowHeight(40);
// 设置为单行选中模式
table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
// 返回当前行的状态模型
ListSelectionModel rowSM = table.getSelectionModel();
// 注册侦听器,选中行发生更改时触发
rowSM.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
// 只处理鼠标按下
if (e.getValueIsAdjusting() == false) {
return;
}
ListSelectionModel lsm = (ListSelectionModel) e.getSource();
if (lsm.isSelectionEmpty()) {
System.out.println("没有选中行");
} else {
int selectedRow = lsm.getMinSelectionIndex();
System.out.println("第" + selectedRow + "行被选中");
}
}
});
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
// 设置窗口大小
setSize(960, 640);
// 计算窗口位于屏幕中心的坐标
int x = (int) (screenWidth - 960) / 2; // (屏幕宽度-窗口宽度) / 2
int y = (int) (screenHeight - 640) / 2;
// 设置窗口位于屏幕中心
setLocation(x, y);
// 设置窗口可见
setVisible(true);
}
// 表格列标题
String[] columnNames = { "书籍编号", "书籍名称", "作者", "出版社", "出版日期", "库存数量" };
// 表格数据
Object[][] rowData = {{ "0036", "高等数学", "李放", "人民邮电出版社", "20000812", 1},
{ "0004", "FLASH精选", "刘扬", "中国纺织出版社", "19990312", 2 },
{ "0026", "软件工程", "牛田", "经济科学出版社", "20000328", 4 },
{ "0015", "人工智能", "周未", "机械工业出版社", "19991223", 3 },
{ "0037", "南方周末", "邓光明", "南方出版社", "20000923", 3 },
...,
{ "0032", "SOL使用手册", "贺民", "电子工业出版社", "19990425", 2 } };
}
第26~40行注册事件监听器,监听器当行选择变化时触发。由于ListSelectionListener接口虽然不是函数式接口,但只有一个方法,所以可以使用Lambda表达式实现该接口:
// 也可换成Lambda表达式
rowSM.addListSelectionListener(e -> {
ListSelectionModel lsm = (ListSelectionModel) e.getSource();
if (lsm.isSelectionEmpty()) {
System.out.println("没有选中行");
} else {
int selectedRow = lsm.getMinSelectionIndex();
System.out.println("第" + selectedRow + "行被选中");
}
});
表格一般都会放到一个滚动面板(JScrollPane)中,这可以保证数据很多超出屏幕时,能够出现滚动条。把表格添加到滚动面板并不是使用add()方法,而是使用第43行的 scrollPane.setViewportView(table)语句。滚动面板是非常特殊的面板,它管理着一个视口或窗口,当里面的内容超出视口会出现滚动条,setViewportView()方法可以设置一个容器或组件作为滚动面板的视口。
案例:图书库存
在进行数据库设计时,数据库中每一个表对应Java一个实体类,实体类是系统的“人”、“事”、“物”等一些名词。
图书(Book)就是一个实体类了,实体类Book代码:
// 图书实体类
public class Book {
// 图书编号
private String bookid;
// 图书名称
private String bookname;
// 图书作者
private String author;
// 出版社
private String publisher;
// 出版日期
private String pubtime;
// 库存数量
private int inventory;
public String getBookid() {
return bookid;
}
public void setBookid(String bookid) {
this.bookid = bookid;
}
public String getBookname() {
return bookname;
}
public void setBookname(String bookname) {
this.bookname = bookname;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author
}
// ...
// 省略Getter和Setter方法
}
实体类有很多私有属性(成员变量),为了在类外部能够访问它们,一般都会提供公有的Getter和Setter方法。
本例表格中的数据是从JSON文件Books.json中读取的,Books.json位于项目的db目录中,JSON文件Books.json的内容:
[{"bookid":"0036","bookname":"高等数学","author":"李放","publisher":"人民邮电出版社","pubtime":"20000812","inventory":1},
{"bookid":"0004","bookname":"FLASH精选","author":"刘扬","publisher":"中国纺织出版社","pubtime":"19990312","inventory":2},
...
{"bookid":"0005","bookname":"java基础","author":"王一","publisher":"电子工业出版社","pubtime":"19990528","inventory":3},
{"bookid":"0032","bookname":"SOL使用手册","author":"贺民","publisher":"电子工业出版社","pubtime":"19990425","inventory":2}]
public class HelloWorld {
public static void main(String[] args) {
List<Book> data = readData();
new MyFrameTable("图书库存", data);
}
// 从文件中读取数据
private static List<Book> readData() {
// 返回的数据列表
List<Book> list = new ArrayList<Book>();
// 数据文件
String dbFile = "./db/Books.json";
try (FileInputStream fis = new FileInputStream(dbFile);
InputStreamReader ir = new InputStreamReader(fis);
BufferedReader in = new BufferedReader(ir)) {
// 1.读取文件
StringBuilder sbuilder = new StringBuilder();
String line = in.readLine();
while (line != null) {
sbuilder.append(line);
line = in.readLine();
}
// 2.JSON解码
// 读取JSON字符完成
System.out.println("读取JSON字符完成...");
// JSON解码,解码成功返回JSON数组
JSONArray jsonArray = new JSONArray(sbuilder.toString());
System.out.println("JSON解码成功完成...");
// 3.将JSON数组放到List<Book>集合中
// 遍历集合
for (Object item : jsonArray) {
JSONObject row = (JSONObject) item;
Book book = new Book();
book.setBookid((String) row.get("bookid"));
book.setBookname((String) row.get("bookname"));
book.setAuthor((String) row.get("author"));
book.setPublisher((String) row.get("publisher"));
book.setPubtime((String) row.get("pubtime"));
book.setInventory((Integer) row.get("inventory"));
list.add(book);
}
} catch (Exception e) {
}
return list;
}
}
- 读取文件。通过Java IO取得文件./db/Books.json,每次读取的字符串保存到StringBuilder的sbuilder对象中。文件读完sbuilder中就是全部的JSON字符串。
- JSON解码:读取JSON字符完成后,需要对其进行解码。由于JSON字符串是数组结构,因此解码时候使用JSONArray,创建JSONArray对象过程就是对字符串进行解码的过程,如果没有发生异常,说明成功解码。
- 将JSON数组放到List<Book>集合中。本例表格使用的数据格式不是JSON数组形式,而是 List<Book>,这种结构就是List集合中每一个元素都是Book类型。这个过程需要遍历JSON数组,把数据重新组装到Book对象中。
模型BookTableModel:
import java.util.List;
import javax.swing.table.AbstractTableModel;
public class BookTableModel extends AbstractTableModel {
// 列名数组
private String[] columnNames = { "书籍编号", "书籍名称", "作者", "出版社", "出版日期", "库存数量" };
// data保存了表格中数据,data类型是List集合
private List<Book> data = null;
public BookTableModel(List<Book> data) {
this.data = data;
}
// 获得列数
@Override
public int getColumnCount() {
return columnNames.length;
}
// 获得行数
@Override
public int getRowCount() {
return data.size();
}
// 获得某行某列的数据
@Override
public Object getValueAt(int row, int col) {
Book book = (Book) data.get(row);
switch (col) {
case 0:
return book.getBookid();
case 1:
return book.getBookname();
case 2:
return book.getAuthor();
case 3:
return book.getPublisher();
case 4:
return book.getPubtime();
case 5:
return new Integer(book.getInventory());
}
return null;
}
// 获得某列的名字
@Override
public String getColumnName(int col) {
return columnNames[col];
}
}
抽象类AbstractTableModel要求必须实现getColumnCount()(提供表格列数)、getRowCount()(提供表格行数)和getValueAt()(指定行和列时单元格内容)三个抽象方法。第51行的getColumnName()方法不是抽象类要求实现的方法,重写该方法能够给表格提供有意义的列名。
窗口代码:
public class MyFrameTable extends JFrame {
// 获得当前屏幕的宽高
private double screenWidth = Toolkit.getDefaultToolkit().
getScreenSize().getWidth();
private double screenHeight = Toolkit.getDefaultToolkit().
getScreenSize().getHeight();
private JTable table;
// 图书列表
private List<Book> data;
public MyFrameTable(String title, List<Book> data) {
super(title);
this.data = data;
TableModel model = new BookTableModel(data);
table = new JTable(model);
// 设置表中内容字体
table.setFont(new Font("微软雅黑", Font.PLAIN, 16));
// 设置表列标题字体
table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 16));
// 设置表行高
table.setRowHeight(40);
// 设置为单行选中模式
table.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
// 返回当前行的状态模型
ListSelectionModel rowSM = table.getSelectionModel();
// 注册侦听器,选中行发生更改时触发
rowSM.addListSelectionListener(e -> {
// 只处理鼠标按下
if (e.getValueIsAdjusting() == false) {
return;
}
ListSelectionModel lsm = (ListSelectionModel) e.getSource();
if (lsm.isSelectionEmpty()) {
System.out.println("没有选中行");
} else {
int selectedRow = lsm.getMinSelectionIndex();
System.out.println("第" + selectedRow + "行被选中");
}
});
JScrollPane scrollPane = new JScrollPane();
scrollPane.setViewportView(table);
getContentPane().add(scrollPane, BorderLayout.CENTER);
// 设置窗口大小
setSize(960, 640);
// 计算窗口位于屏幕中心的坐标
int x = (int) (screenWidth - 960) / 2;
int y = (int) (screenHeight - 640) / 2;
// 设置窗口位于屏幕中心
setLocation(x, y);
// 设置窗口可见
setVisible(true);
}
}