Java核心技术1-GUI基础

  |  

摘要: 本文是《Java核心技术 10th》中关于 GUI 基础的要点总结。

【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


本文是《Java核心技术1》第10版 Chap13 中关于 GUI 基础的要点总结。

本文只记录一些关键组件的各个类之间的关系,以及主要功能的示例代码。具体如下:

(1) 最简单的可见框架
(2) 根据屏幕分辨率设定 Frame 大小
(3) 在组件中显示信息
(4) 绘制 2D 图形
(5) 使用颜色和字体
(6) 显示图像


基础

现在一般选择 Swing,优点如下

  • Swing 拥有一个丰富、便捷的用户界面元素集合。
  • Swing 对底层平台依赖的很少,因此与平台相关的 bug 很少。
  • Swing 给予不同平台的用户一致的感觉。

在 Swing 中可以指定专门的“观感”。例如 Window 观感、GTK 观感等。

Swing 没有完全替代 AWT,而是基于 AWT 架构之上。Swing 仅仅提供了能力更加强大的用户界面组件。在采用Swing 编写的程序中,还需要使用基本的 AWT 处理事件

在 Java 中,顶层窗口(就是没有包含在其他窗口中的窗口)被称为框架(frame)。在 AWT 库中有一个称为 Frame 的类,用于描述顶层窗口。这个类的 Swing 版本名为 JFrame,它扩展于 Frame 类。JFrame 是极少数几个不绘制在画布上的 Swing 组件之一。因此,它的修饰部件(按钮、标题栏、图标等)由用户的窗口系统绘制,而不是由 Swing 绘制。

绝大多数 Swing 组件类都以 “J” 开头,例如 JButton、JFrame 等。在 Java 中有 Button 和 Frame 这样的类,但它们属于 AWT 组件。


JFrame

JFrame 中从超类继承的处理大小和位置的方法:

  • setLocation 和 setBounds 方法用于设置框架的位置。
  • setIconImage 用于告诉窗口系统在标题栏、任务切换窗口等位置显示哪个图标。
  • setTitle 用于改变标题栏的文字。
  • setResizable 利用一个 boolean 值确定框架的大小是否允许用户改变。

AWT 和 Swing 中框架和组件类的继承层次:

JFrame 内部结构:四层面板。其中的根面板、层级面板和玻璃面板人们并不太关心;它们是用来组织菜单栏和内容窗格以及实现观感的。程序员最关心的是内容窗格(contentpane)。

向内容窗格中添加组件:

1
2
3
Container contentPane = frame.getContentPane();
Component c = ...;
contentPane.add(c);

图形类之间的关系


代码模板

(1) 最简单的可见框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package simpleFrame;

import java.awt.EventQueue;
import javax.swing.JFrame;

public class SimpleFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

class SimpleFrame extends JFrame {
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;

public SimpleFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}

代码要点

javax 是 java 扩展包,而不是核心包。

在默认情况下,框架的大小为 0x0 像素,因此扩展 JFrame 并设置 300x200 像素。

main 中,构造一个 SimpleFrame 对象并使其可见。

所有的Swing组件必须由事件分派线程(event dispatchthread)进行配置,线程将鼠标点击和按键控制转移到用户接口组件。

下面代码片段是事件分派线程中的执行代码:

1
2
3
EventQueue.invokeLater(() -> {
...
})

定义一个用户关闭这个框架时的响应动作。对于这个程序而言,只让程序简单地退出即可。

1
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

在初始化语句结束后,main方法退出。需要注意,退出main并没有终止程序,终止的只是主线程。事件分派线程保持程序处于激活状态,直到关闭框架或调用System.exit方法终止程序。

(2) 根据屏幕分辨率设定 Frame 大小

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
29
30
31
32
33
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.Dimension;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;

public class SizedFrameTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new SizedFrame();
frame.setTitle("SizedFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

class SizedFrame extends JFrame {
public SizedFrame() {
// 获取屏幕分辨率
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;

setSize(screenWidth / 2, screenHeight / 2);
setLocationByPlatform(true);

Image img = new ImageIcon("icon.png").getImage();
setIconImage(img);
}
}

(3) 在组件中显示信息

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JComponent;

public class NotHelloWorld {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new NotHelloWorldFrame();
frame.setTitle("NotHelloWorld");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

/**
* 消息面板的 Frame
*/
class NotHelloWorldFrame extends JFrame {
public NotHelloWorldFrame() {
add(new NotHelloWorldComponent());
pack();
}
}

/**
* 显示消息的 component
*/
class NotHelloWorldComponent extends JComponent {
public static final int MESSAGE_X = 75;
public static final int MESSAGE_Y = 100;

private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 200;

public void paintComponent(Graphics g) {
g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y);
}

public Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}

(4) 绘制 2D 图形

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JComponent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Line2D;

public class DrawTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new DrawFrame();
frame.setTitle("DrawTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

/**
* 画图的面板
*/
class DrawFrame extends JFrame {
public DrawFrame() {
add(new DrawComponent());
pack();
}
}

/**
* 显示矩形和椭圆的 component
*/
class DrawComponent extends JComponent {
private static final int DEFAULT_WIDTH = 400;
private static final int DEFAULT_HEIGHT = 400;

public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;

// 矩形
double leftX = 100;
double topY = 100;
double width = 200;
double height = 150;
Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
g2.draw(rect);

// 椭圆
Ellipse2D ellipse = new Ellipse2D.Double();
ellipse.setFrame(rect);
g2.draw(ellipse);

// 对角线
g2.draw(new Line2D.Double(leftX, topY, leftX + width, topY + height));

// 圆形
double centerX = rect.getCenterX();
double centerY = rect.getCenterY();
double radius = 150;

Ellipse2D circle = new Ellipse2D.Double();
circle.setFrameFromCenter(centerX, centerY, centerX + radius, centerY + radius);
g2.draw(circle);
}

public Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}

(5) 使用颜色和字体

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Font;
import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JComponent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Line2D;

public class FontTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new FontFrame();
frame.setTitle("FontFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

class FontFrame extends JFrame {
public FontFrame() {
add(new FontComponent());
pack();
}
}

class FontComponent extends JComponent {
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;

public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;

String message = "Hello, World!";

Font f = new Font("Serif", Font.BOLD, 36);
g2.setFont(f);

// 测量 message 的尺寸
FontRenderContext context = g2.getFontRenderContext();
Rectangle2D bounds = f.getStringBounds(message, context);

// 将 (x, y) 设为左上角
double x = (getWidth() - bounds.getWidth()) / 2;
double y = (getHeight() - bounds.getHeight()) / 2;

double ascent = -bounds.getY();
double baseY = y + ascent;

g2.drawString(message, (int) x, (int) baseY);
g2.setPaint(Color.RED);

g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY));

Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight());
g2.draw(rect);
}

public Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}

(6) 显示图像

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import java.awt.EventQueue;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Graphics;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JComponent;

public class ImageTest {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new ImageFrame();
frame.setTitle("ImageTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}

class ImageFrame extends JFrame {
public ImageFrame() {
add(new ImageComponent());
pack();
}
}

class ImageComponent extends JComponent {
private static final int DEFAULT_WIDTH = 1080;
private static final int DEFAULT_HEIGHT = 1440;

private Image image;

public ImageComponent() {
image = new ImageIcon("icon.gif").getImage();
}

public void paintComponent(Graphics g) {
if(image == null) {
return;
}
int imageWidth = image.getWidth(this);
int imageHeight = image.getHeight(this);

// 在左上角画图
g.drawImage(image, 0, 0, null);

for(int i = 0; i * imageWidth <= getWidth(); i++) {
for(int j = 0; j * imageHeight <= getHeight(); j++) {
if(i + j > 0) {
g.copyArea(0, 0, imageWidth, imageHeight, i * imageWidth, j * imageHeight);
}
}
}
}

public Dimension getPreferredSize() {
return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
}

API 总结

java.awt.Component

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
29
30
31
32
// 获取或设置 visible 属性。组件最初是可见的,但 JFrame 这样的顶层组件例外。
boolean isVisible()
void setVisible(boolean b)

// 使用给定的宽度和高度,重新设置组件的大小。
void setSize(int width, int height)

// 将组件移到一个新的位置上。如果这个组件不是顶层组件,x 和 y 坐标(或者 p.x 和 p.y)是容器坐标;否则是屏幕坐标(例如:JFrame)。
void setLocation(int x, int y)

// 移动并重新设置组件的大小。
void setBounds(int x, int y, int width, int height)

// 获取或设置当前组件的 size 属性。
Dimension getSize()
void setSize(Dimension d)

// “尽可能快地”重新绘制组件。
void repaint()

// 要覆盖这个方法,返回这个组件的首选大小。
Dimension getPreferredSize()

// 获取或设置背景颜色。
// 参数:c 新背景颜色
Color getBackground()
void setBackground(Color c)

// 获取或设置前景颜色。
// 参数:c 新前景颜色
Color getForeground()
void setForeground(Color c)

javax.swing.JComponent

1
2
3
4
5
// 覆盖这个方法来描述应该如何绘制自己的组件。
void paintComponent(Grphics g)

// 获取给定字体的度量。FontMetrics 类是LineMetrics 类的早先版。
FontMetrics getFontMetrics(Font f)

java.awt.Window

1
2
3
4
5
6
7
8
9
10
11
12
// 将这个窗口显示在其他窗口前面。
void toFront()

// 将这个窗口移到桌面窗口栈的后面,并相应地重新排列所有的可见窗口。
void toBack()

// 获取或设置 locationByPlatform 属性。这个属性在窗口显示之前被设置,由平台选择一个合适的位置。
boolean isLocationByPlatform()
void setLocationByPlatform(boolean b)

// 调整窗口大小,要考虑到其组件的首选大小。
void pack()

java.awt.Frame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 获取或设置 resizable 属性。这个属性设置后,用户可以重新设置框架的大小。
boolean isResizable()
void setResizable(boolean b)

// 获取或设置 title 属性,这个属性确定框架标题栏中的文字。
String getTitle()
void setTitle(String s)

// 获取或设置 iconImage 属性,这个属性确定框架的图标。窗口系统可能会将图标作为框架装饰或其他部位的一部分显示。
Image getIconImage()
void setIconImage(Image image)

// 获取或设置 undecorated 属性。这个属性设置后,框架显示中将没有标题栏或关闭按钮这样的装饰。在框架显示之前,必须调用这个方法。
boolean isUndecorated()
void setUndecorated(boolean b)

// 获取或设置窗口状态。状态是下列值之一。
// Frame.NORMAL
// Frame.ICONIFIED
// Frame.MAXIMIZED_HORIZ
// Frame.MAXIMIZED_VERT
// Frame.MAXIMIZED_BOTH
int getExtendedState()
void setExtendedState(int state)

javax.swing.JFrame

1
2
3
4
5
// 返回这个 JFrame 的内容窗格对象。
Container getContentPane()

// 将一个给定的组件添加到该框架的内容窗格中。
Component add(Component c)

java.awt.Toolkit

1
2
3
4
5
// 返回默认的工具箱。
static Toolkit getDefaultToolkit()

// 返回用户屏幕的尺寸。
Dimension getScreenSize()

javax.swing.ImageIcon

1
2
3
4
5
// 构造一个图标,其图像存储在一个文件中。
ImageIcon(String filename)

// 获得该图标的图像。
Image getImage()

java.awt.geom.RectangularShape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回闭合矩形的中心,以及最小、最大 x 和 y 坐标值。
double getCenterX()
double getCenterY()
double getMinX()
double getMinY()
double getMaxX()
double getMaxY()

// 返回闭合矩形的宽和高。
double getWidth()
double getHeight()

// 返回闭合矩形左上角的 x 和 y 坐标。
double getX()
double getY()

java.awt.geom.Rectangle2D.Double

1
2
// 利用给定的左上角、宽和高,构造一个矩形。
Rectangle2D.Double(double x, double y, double w,double h)

java.awt.geom.Rectangle2D.Float

1
2
// 利用给定的左上角、宽和高,构造一个矩形。
Rectangle2D.Float(float x, float y, float w, float h)

java.awt.geom.Ellipse2D.Double

1
2
// 利用给定的左上角、宽和高的外接矩形,构造一个椭圆。
Ellipse2D.Double(double x, double y, double w, doubleh)

java.awt.geom.Point2D.Double

1
2
// 利用给定坐标构造一个点。
Point2D.Double(double x, double y)

java.awt.geom.Line2D.Double

1
2
3
// 使用给定的起点和终点,构造一条直线。
Line2D.Double(Point2D start, Point2D end)
Line2D.Double(double startX, double startY, doubleendX, double endY)

java.awt.Color

1
2
3
// 创建一个颜色对象。
// 参数:r 红色值(0-255)g 绿色值(0-255)b 蓝色值(0-255)
Color(int r, int g, int b)

java.awt.Graphics

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
29
30
31
32
33
34
35
36
// 获取或改变当前的颜色。所有后续的绘图操作都使用这个新颜色。参数:c 新颜色
Color getColor()
void setColor(Color c)

// 获取或设置当前的字体。这种字体将被应用于后续的文本绘制操作中。参数:font 一种字体
Font getFont()
void setFont(Font font)

// 采用当前字体和颜色绘制一个字符串。
// 参数:str 将要绘制的字符串x 字符串开始的x坐标y 字符串基线的y坐标
void drawString(String str, int x, int y)

// 绘制一幅非比例图像。注意:这个调用可能会在图像还没有绘制完毕就返回。参数:
// img 将要绘制的图像
// x: 左上角的x坐标
// y: 左上角的y坐标
// observer: 绘制进程中以通告为目的的对象(可能为null)。
boolean drawImage(Image img, int x, int y, ImageObserver observer)

// 绘制一幅比例图像。系统按照比例将图像放入给定宽和高的区域。注意:这个调用可能会在图像还没有绘制完毕就返回。参数:
// img: 将要绘制的图像
// x: 左上角的 x 坐标
// y: 左上角的 y 坐标
// width: 描述图像的宽度
// height: 描述图像的高度
// observer: 绘制进程中以通告为目的的对象(可能为null)
boolean drawImage(Image img, int x, int y, int width, intheight, ImageObserver observer)

// 拷贝屏幕的一块区域。参数:
// x: 原始区域左上角的x坐标
// y: 原始区域左上角的y坐标
// width: 原始区域的宽度
// height: 原始区域的高度
// dx: 原始区域到目标区域的水平距离
// dy: 原始区域到目标区域的垂直距离
void copyArea(int x, int y, int width, int height, int dx, int dy)

java.awt.Graphics2D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取或设置这个图形环境的绘制属性。
// Color 类实现了 Paint 接口。因此,可以使用这个方法将绘制属性设置为纯色。
Paint getPaint()
void setPaint(Paint p)

// 用当前的颜料填充该图形。
void fill(Shape s)

// 返回这个图形文本中,指定字体特征的字体绘制环境。
FontRenderContext getFontRenderContext()

// 采用当前的字体和颜色绘制一个字符串。
// 参数:str 将要绘制的字符串 x 字符串开始的 x 坐标 y 字符串基线的 y 坐标
void drawString(String str, float x, float y)

java.awt.Font

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
// 创建一个新字体对象。参数:
// name: 字体名。不是字体名(例如,“Helvetica Bold”),就是逻辑字体名(例如,“Serif”、“SansSerif”)
// style: 字体风格(Font.PLAIN、Font.BOLD、Font.ITALIC或Font.BOLD+Font. ITALIC)
// size: 字体大小(例如,12)
Font(String name, int style, int size)

// 返回字体名,例如,“Helvetica Bold”。
String getFontName()

// 返回字体家族名,例如,“Helvetica”。
String getFamily()

// 如果采用逻辑字体名创建字体,将返回逻辑字体,例如,“SansSerif”;否则,返回字体名。
String getName()

// 返回包围这个字符串的矩形。矩形的起点为基线。矩形顶端的y坐标等于上坡度的负值。矩形的高度等于上坡度、下坡度和行间距之和。宽度等于字符串的宽度。
Rectangle2D getStringBounds(String s,FontRenderContext context)

// 返回测定字符串宽度的一个线性 metrics 对象。
LineMetrics getLineMetrics(String s, FontRenderContextcontext)

// 返回一个新字体,除给定大小和字体风格外,其余与原字体一样
Font deriveFont(int style)
Font deriveFont(float size)
Font deriveFont(int style, float size)

java.awt.font.LineMetrics

1
2
3
4
5
6
7
8
9
10
11
// 返回字体的上坡度——从基线到大写字母顶端的距离。
float getAscent()

// 返回字体的下坡度——从基线到坡底的距离。
float getDescent()

// 返回字体的行间距——从一行文本底端到下一行文本顶端之间的空隙。
float getLeading()

// 返回字体的总高度——两条文本基线之间的距离(下坡度+行间距+上坡度)。
float getHeight()

java.awt.FontMetrics

1
2
// 返回字体的字体绘制环境。
FontRenderContext getFontRenderContext()

Share