软件构造学习记录——Swing
Swing学习记录
前言
Java 1.0刚刚出现时,包含了一个用于基本 GUI 程序设计的类库,被称为抽象窗口工具箱(Abstract Window Toolkit,AWT),然而不同平台上运行的AWT用户界面库存在不同的bug,后来Sun公司和Netscape合作,创建了一个名为Swing的用户界面库。值得注意的是,Swing没有完全取代AWT,而是基于AWT构架之上,Swing仅仅提供了更强大的用户界面组件,处理事件还需要AWT
下面通过一个例子来创建基于Swing的GUI界面
创建框架
在Java中,顶层窗口(就是没有包含在其他窗口中的窗口)被称为框架。在Swing中用JFrame来描述顶层窗口,下面的例子可以创建一个宽高均为屏幕一半的界面
package GUI;
import javax.swing.*; //Swing位于javax.swing包
import java.awt.*;
public class Frame {
public static void main (String[] args) {
EventQueue.invokeLater(() ->
{
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenWith = screenSize.width;
int screenHeight = screenSize.height;
MyFrame frame = new MyFrame(screenWith / 2, screenHeight / 2); // 宽高均为一半
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 定义关闭框架的动作
frame.setVisible(true); // 设置可见性
});
}
}
class MyFrame extends JFrame {
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;
public MyFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public MyFrame(Dimension dimension) {
setSize(dimension);
}
public MyFrame(int width, int height) {
setSize(width, height);
setTitle("Board"); // 框架的标题
}
}
运行时会创建一个框架,标题是"Board",如下图。采用EventQueue.invokeLater(() -> { statement; }要比直接在主线程中完成初始化更加安全(虽然不知道原理)
框架的定位及部分属性
JFrame 类本身只包含若干个改变框架外观的方法,其他许多处理框架大小和位置的方法是从其超类中继承来的,下图是JFrame类的继承层次图,图片取自《Java核心技术卷I》⬇️(后面的图片也是)
比较重要的设置框架位置、大小的方法有:
- setLocation(int x, int y),setBounds(int x, int y, int width, int height):这两个方法都是从Component类中继承得来的,setLoction方法将框架的左上角设为(x,y)位置,屏幕左上角为(0,0);setBounds同时将窗口大小设置为width×height
- setLocationByPlatform(boolean bool):是否让窗口系统选择窗口的位置,如果bool为true,窗口系统可以控制窗口的位置,通常是距离最后一个显示窗口很少偏移量的位置
- setSize(int width, int height),setSize(Dimension dimension):设置窗口大小
- setIconImage(Image image):设置图标(ALT+TAB时候显示的图标)
- setTitle(String title):设置标题
- setResizable(boolean bool):设置用户是否可以重新设置框架大小
JFrame的结构
可以直接将消息绘制在框架中,然而并不推荐这样。在Java中,框架被设计为放置组件的容器,所以在通常情况下,应该在一个组件上绘制信息,再将这个组件添加到框架中。
JFrame包含四层面板,其中根面板、层级面板和玻璃面板使用来组织菜单栏和内容窗格以及实现观感的,编程时并不太关心它们,而关心内容窗格Content Pane,在编程时使用下列代码将组件添加到内容窗格中
Container contentPane = frame.getContentPane();
Component c = ...;
contentPane.add(c);
在Java SE 1.4及以前版本中,使用JFrame类中的add方法时会抛出异常"Do not use JFrame.add().Use JFrame.getContentPane.add() instead",如今JFrame.add会调用内容窗格的add,所以可以直接调用
frame.add(c);
加到框架中的组件c,必须是实现了paintComponent方法的类,该方法表示这个组件如何进行绘制
Swing组件
首先来看一下各个组件的类层次
布局管理
布局管理顾名思义就是管理各个组件,使其按照一定的模式排列在一个框架或容器内
每个容器都有一个默认的布局管理器,但可以使用setLayout()方法重新进行设置,如
panel.setLayout(new GridLayout(2, 2)); // 将布局设置为网格布局
JPanel对象默认采用流布局管理器(flow layout manager),当一行的空间不够时,会将显示在新的一行上。通常,组件放置在容器中, 布局管理器决定容器中的组件具体放置的位置和大小,下面介绍两种布局
-
边框布局(BorderLayout)
边框布局管理器(border layout manager)是每个JFrame的内容窗格的默认布局管理器,与流布局管理器不同的是,边框布局管理器不会完全控制每个组件的放位置,它允许为每个组件选择一个放置位置。可以将组件放置在,东南西北或中,5种位置,如下图
5个位置可以不填满,当容器被缩放时,四边的组件的大小不会发生改变,而中间组件的大小会发生变化。与流布局不同,边框布局会扩展所有组件的尺寸以便填满可用空间,而流布局将维持每个组件的最佳尺寸。 -
网格布局(GridLayout)
网格布局像电子数据表一样,按行列排列所有的组件。不过,它的每个单元大小都是一样的,当缩放窗口时,所有组件的大小都会变化,但是它们的大小始终保持一致
文本
具有用户输入和编辑文本功能的组件有三种:
- JTextField:文本域,只能接受单行文本的输入
- JTextArea:文本区,可以接受多行文本的输入
- JPasswordField:密码域,只能接受单行文本的输入,且不会显示出原内容,而是以回显字符(echo character)表示,比如’*’
上面三种组件均继承于抽象类JTextComponent,下面一一介绍其用法:
-
文本域
JPanel panel = new JPanel(); JTextField textField = new JTextField("Default input", 15); panel.add(textField); frame.add(panel);
上面采用将文本域加入到面板,再将面板加入框架的方法,也可以直接将文本域加入到框架中。第二行构造JTextField用到了两个参数,第一个参数指示初始字符串,可以省略,第二个参数指示文本域的列数。可以使用getColumns()方法获取列数,用setColumns(int cols)方法来重新设置列数,需要注意的是使用setColumns方法改变了一个文本域的大小之后,需要调用包含这个文本框的容器(上面的例子中这个容器是panel)的revalidate方法。该方法会重新计算容器内所有组件的大小,并且对它们重新进行布局。
可以通过setText(String str)方法改变文本域中的内容,也可以用getText()方法获得用户输入的文本,如果嫌弃字体难看可以用setFont(Font f)方法设置组件的字体 -
文本区
JPanel panel = new JPanel(); JTextArea textArea = new JTextArea(8, 15); // 8 行 15 列 panel.add(textField); frame.add(panel);
文本区用于接收超过一行的用户输入,setRows和setColumns可以改变行数和列数,布局管理器也可能对文本区进行缩放(比如一行输入巨长时,文本区的列数会变化;行数同理)。当一行输入文本巨长时,文本区会变得很长,可以通过开启换行特性来避免过长
textArea.setLineWrap(true);
这样当一行输入文本长度比文本区的列数大时,该行文本会被自动换行。值得注意的是,换行只是视觉效果,文档中的文本没有改变,文本中并没有插入’\n’字符。
-
密码域
JPanel panel = new JPanel(); JPasswordField password = new JPasswordField(15); panel.add(password); frame.add(panel);
如果你看不顺眼默认的回显字符,可以用setEchoChar(char echo)方法设置。同时,可以用getPassword()方法返回密码域中的文本,为了安全起见,使用后应该覆盖返回数组的内容。值得注意的是,该方法返回的是char[]而不是String,因为字符串对象在被垃圾回收器回收之前会驻留在虚拟机中
下图给出了三个组件的显示效果
与文本有关的还有标签(JLabel)组件,它可以用于标识其他组件。
在文本区中,如果输入长度(不管是列方向还是行方向)超过文本区设定的大小都会扩张文本区(文本域和密码域不会),使用setLineWrap方法会让一行文本分行显示,然而有些用户并不喜欢这样,它们更倾向于使用滚动条。然而文本区中没有滚动条,不过可以将文本区插入到滚动窗格(scroll pane)中
textArea = new JTextArea(8, 15);
JScrollPane scrollPane = new JScrollPane(textArea);
效果如图所示:
这是一种为任意组件添加滚动功能的通用机制, 而不是文本区特有的。也就是说,要想为组件添加滚动条,只需将它们放入一个滚动窗格中即可
(读到这实验3的GUI应该差不多可以完成,其他的组件做完实验再加,咕咕咕)