写在前面

由于最近在做编译原理实验的作业——制作八、十、十六进制计算器,想要得高分需要做个界面。

于是乎想到之前有学过的Java的Swing组件(简单易用),不过也有些忘记了,就顺手写篇博客记录一下制作过程以及期间遇到的细节问题(

So,这篇博客讲的更多的会是计算器怎么做啦。

注意:由于本人用的是macOS系统,默认组件显示效果会与其他系统有所不同。

持续更新中……

前期工作

在使用Swing组件前,你至少需要了解JAVA的基本语法,以及常见错误排查。

那么,先让我们大致了解一下Java Swing吧。

什么是Swing组件?

注意:以下部分内容摘自菜鸟教程 - Java Swing 介绍

Swing 是一个为Java设计的GUI工具包。

Swing是Java基础类的一部分。

Swing包括了图形用户界面(GUI)器件如:文本框,按钮,分隔窗格和表。

Swing提供许多比AWT更好的屏幕显示元素。它们用纯Java写成,所以同Java本身一样可以跨平台运行,这一点不像AWT。它们是JFC的一部分。它们支持可更换的面板和主题(各种操作系统默认的特有主题),然而不是真的使用原生平台提供的设备,而是仅仅在表面上模仿它们。这意味着你可以在任意平台上使用JAVA支持的任意面板。轻量级组件的缺点则是执行速度较慢,优点就是可以在所有平台上采用统一的行为。

它包含些什么?

JFrame – java的GUI程序的基本思路是以JFrame为基础,它是屏幕上window的对象,能够最大化、最小化、关闭。

JPanel – Java图形用户界面(GUI)工具包swing中的面板容器类,包含在javax.swing 包中,可以进行嵌套,功能是对窗体中具有相同逻辑功能的组件进行组合,是一种轻量级容器,可以加入到JFrame窗体中。

JLabel – JLabel 对象可以显示文本、图像或同时显示二者。可以通过设置垂直和水平对齐方式,指定标签显示区中标签内容在何处对齐。默认情况下,标签在其显示区内垂直居中对齐。默认情况下,只显示文本的标签是开始边对齐;而只显示图像的标签则水平居中对齐。

JTextField – 一个轻量级组件,它允许编辑单行文本。

JButton – JButton 类的实例。用于创建按钮。

……

当然,Swing还有很多其他的组件,这里只列(抄)出一些常用组件。

这些组件按照不同的功能,可分为顶层组件、中间容器和基本组件,其层级结构为:

  • 顶层容器
    • 菜单栏(特殊中间容器)
    • 中间容器
      • 基本组件

如果破坏这个结构去绘制窗口,可能会导致出现一些奇怪的错误。

可以点击这里了解更多。

尝试编写

JFrame - 一切的开端

在编写任何GUI窗口以前,我们需要先创建一个JFrame实例,使得这个窗口能够在系统中显示。

这个JFrame便是我们上文有提到的顶层容器之一。

JFrame的常用默认构造方法有两个:

  • JFrame() 创建默认无标题窗口

  • JFrame(String) 创建带标题窗口

那么我们就可以这样new一个JFrame对象,并设置让它显示:

1
2
3
4
5
6
7
8
9
10
11
import javax.swing.*; // 引入Swing组件

public class Test {

public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.setSize(500, 500); // 设置窗口大小
jFrame.setVisible(true); // 设置为可显示
}

}

运行后会出现如下图的窗口:

新建窗口

当然也可以采用继承的方式(推荐):

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

public class Test extends JFrame {

Test() {
this.setSize(500, 500);
this.setVisible(true);
}

public static void main(String[] args) {
new Test();
}

}

使用继承的方式可以让我们更好地去修改这个JFrame窗口及其内含组件,大家也可能发现了,这里用到了两个方法,这是JFrame里默认定义的一些方法,合理使用它们可以更好地DIY你的JFrame,下面是一些常用方法:

方法 概述
setTitle(String) 设置当前JFrame的标题
setResizable(boolean) 设置当前JFrame可否调整大小
setDefaultCloseOperation(int) 设置当前JFrame点击关闭按钮的动作
setVisible(boolean) 设置当前JFrame是否可见
setSize(int, int) 设置当前JFrame宽高
setLocationRelativeTo(Component) 设置当前JFrame位置,null居中
Insets getInsets() 返回当前JFrame边框元素
add(Object) 往当前JFrame添加组件

Q:为什么推荐使用继承的方式?

A:继承方式可以让我们更好地自定义(或者说DIY)JFrame,包括一些配置以及添加相关组件、监听事件等,对于Swing中含有的其他组件,也是推荐这样,当然前提是你需要DIY这个组件。

JPanel - 重点基础

创建好窗口后,我们便需要往这个JFrame里添加中间容器,JPanel便是其中之一。

它就是一个普通的面板,可以往其中添加任意基本组件,甚至可以嵌套JPanel,如果是了解、学习过HTML的读者,可以大致地将其理解为<div>标签,而JFrame则大致是<body>标签。

JPanel的常用的构造方法有:

  • JPanel() 创建默认使用流式布局的面板
  • JPanel(LayoutManager) 创建指定布局的面板

因为JPanel可以添加组件,所以JPanel里也有一些和JFrame类似的方法,例如setSizeadd

不过除了普通的面板,Swing组件里还提供了这些:

面板组件 概述
JScrollPanel 可滚动显示的面板
JSplitPane 分隔面板
JTabbedPane 选项卡面板
JLayeredPane 层级面板

由于面板在不添加其他组件时,并不会显示任何,在没有介绍其他组件以前,这里便不做过多介绍。但需要注意的是,面板很重要,对于组件布局这件头疼的事情来说,它起到了一个很好地中间作用(类似HTML中<div>的重要性)。

JButton - 初识布局

有了界面,怎么能让它空空如也呢?第一件事当然是往窗口里加图标!

JButton常用的构造方法有:

  • JButton() 创建无内容的按钮
  • JButton(String) 创建含文本内容的按钮
  • JButton(Icon) 创建有图标的按钮

这里再介绍一下JButton的常用方法:

方法 概述
setText(String) 设置按钮显示的文本
setFont(Font) 设置按钮显示的文本的字体
setForeground(Color) 设置按钮文本的字体颜色
setIcon(Icon) 设置按钮默认图标
setPressedIcon(Icon) 设置按下时图标
setDisabledIcon(Icon) 设置不可用时图标
addActionListener(ActionListener) 添加监听事件
doClick() 模拟点击

那么根据这些方法,我们就可以尝试着new一个JButton,添加到我们之前创建好的JFrame里去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.swing.*;

public class Template extends JFrame {

Template() {
this.setSize(500 ,500);
this.setVisible(true); // 设置为可显示
JButton jButton = new JButton("点我点我");
this.add(jButton);
}

public static void main(String[] args) {
new Template();
}

}

显示效果如下:

JButton示例1

可以看到,我们没有对JButton、JFrame做任何配置时,往JFrame直接添加JButton组件会占满整个窗口。

甚至是添加3、4个JButton时,也可能会被最后被添加的那一个占满。

这里会涉及到两个概念:设置大小、组件布局。

首先是布局——这一概念非常重要,之后会专门讲到,这里先提一下,在Swing组件中常用的布局管理器:

布局管理器 描述
FlowLayout 流式布局,按组件加入顺序水平方向排列到当前组件,排满一行换下一行。
GirdLayout 网格布局,将当前组件按指定行列数用网格分隔,每个网格放一个组件。
GridBagLayout 网格袋布局,按网格将当前组件分割,每个组件可以占一个或多个网格,每个组件所占的网格大小可以用水平线和垂直线自定义。
BoxLayout 箱式布局,将当前组件中的多个组件按水平或垂直方式排列。
GroupLayout 分组布局,将当前组件按层次分组(串行或并行),分别确定组件组在水平和垂直方向的位置。
CardLayout 卡片布局,将当前组件中的每个组件看作一张卡片,一次只显示一张,默认显示第一张。
BorderLayout 边界布局,把当前组件按东西南北中五个方位分割,每个区域放一个组件。
SpringLayout 弹性布局,通过定义组件四条边的坐标位置来实现布局。
null 空(绝对)布局,对每个组件设置位置、大小放置到当前组件中。

注意:如果当前组件没有设置任何布局管理器,默认使用FlowLayout(JFrame除外,它会默认使用BorderLayout)。

对于大多数组件来说,setSize这个方法是可以直接用的,但若是这个组件被放到一个有布局管理器的组件时,这个方法是会失效的,我们需要用上另外一个方法来定义它的大小:

  • setPreferredSize(Dimension)

这个方法并不算Swing组件中的一个,而是Swing的前身——awt中的一个方法,它会获取当前空间的preferredsize,它会自己选择最好的大小,并且随着外部空间的大小改变,而setSize是固定大小,两者各有用处,这里我们只需要先了解到有布局管理器的情况下需要用setPreferredSize即可。

这里举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import javax.swing.*;
import java.awt.*;

public class Template extends JFrame {

Template() {
this.setSize(500 ,500);
JPanel jPanel = new JPanel();
JButton jButton1 = new JButton("1");
jButton1.setPreferredSize(new Dimension(100, 100));
// 分别对应宽、高
jPanel.add(jButton1);
this.add(jPanel);
this.setVisible(true); // 设置为可显示
}

public static void main(String[] args) {
new Template();
}

}

JButton示例2

可以很清楚地看到,按钮成功地缩小嘞。但是会发现,这个按钮被莫名其妙放到了中间?

为之前jPanel对象设置边框后,我们会惊讶地发现,这个JPanel居然占满了整个窗口,甚至是设置了大小也一样?

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

public class Template extends JFrame {

Template() {
this.setSize(500, 500);
JPanel jPanel = new JPanel();
jPanel.setPreferredSize(new Dimension(200, 200));
jPanel.setBorder(BorderFactory.createEtchedBorder()); // 加上边框
JButton jButton1 = new JButton("1");
JButton jButton2 = new JButton("2");
jButton1.setPreferredSize(new Dimension(100, 100));
jButton2.setPreferredSize(new Dimension(100, 100));
jPanel.add(jButton1);
jPanel.add(jButton2);
this.add(jPanel);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
// 设置点击关闭会退出程序
this.setVisible(true); // 设置为可显示

}

public static void main(String[] args) {
new Template();
}

}

JButton示例3

这又回到了之前提到的布局概念,由于JFrame默认是BorderLayout布局,分为东西南北中五块,每一个组件会默认占满一块(无法自定大小),而我把JPanel添加到JFrame时没有定义位置,所以会默认填满整个窗口。

如果对JFrame设置其他布局管理器,就可以避免这个情况。