---
url: /doc/Java/xsqqr43d/index.md
---
> 图形用户界面（Graphical User Interface，简称 GUI）是大多数程序不可缺少的部分。
>
> Java 的图形界面由各种组件组成。在 java.awt 和 java.swing 包中定义了多种用于创建图形用户界面的组件类。
>
> Swing 组件和 AWT 组件最大的不同是 Swing 组件实现时不包含任何本地代码，因此不受硬件平台的限制，而具有更多的功能，并且在任何平台上运行效果都是相同的。不包含本地代码的 Swing 组件被称为 “轻量级” 组件，而 AWT 被称为 “重量级” 组件。两种组件一同使用的场合，AWT 总是显示在上面。

设计用户界面是一般有 3 个步骤：

1. 选取组件
2. 设计布局
3. 响应事件

## 15.1 容器

> 组件分为容器组件和非容器组件。容器组件即可以包含其他组件的组件。非容器组件必须包含在容器组件中。
>
> 容器组件分为：顶层容器、一般用途容器

### 15.1.1 顶层容器

Swing 中提供了 4 种顶层容器：

* JFrame：带有标题行和控制按钮（最小化、恢复/最大化、关闭）的独立窗口。有时称为框架。创建程序时要使用 JFream
* JApplet：创建小应用程序时要使用 JApplet。其被包含在浏览器窗口中
* JDialog：创建对话框时使用 JDialog
* JWindow：一个不带有标题行和控制按钮的窗口。一般很少使用

构造方法：

* `new JFrame()`：构造一个初始不可见，无标题的新框架窗体
* `new JFrame("Title")`：构造一个初始不可见，具有指定标题的新框架窗体

常用方法：

* `void setBounds(int x, int y, int width, int height)`：移动并调整框架大小。框架左上角位置为（x, y），框架大小为 width × height

* `void setSize(int width, int height)`：设置窗口大小

* `void setBackground(Color bg)`：设置框架背景色

  `void setForeground(Color fg)`：设置框架前景色

* `void setVisible(boolean aFlag)`：设置可见性

* `void pack()`：自适应框架大小，以符合子组件的首选大小和布局

* `void setTitle(String title)`：设置标题

* `Container getContentPane()`：返回此框架窗体的内容窗格对象

  *Container 类是所有容器类的父类，包含容器的共有操作*

  `void setContentPane(Container contentPane)`：把指定内容窗格设置为此框架的内容窗格对象

* `void setLayout(LayoutManager manager)`：设置布局管理器

  ***—— 哦我的上帝啊，如果有什么名词是你不懂的，为什么不试着继续看下去呢？***

### 15.1.2 内容窗格

> 4 个顶层容器每个都有 1 个内容窗格。除菜单外，顶层容器的所有组件都放在这个内容窗格内。

* 直接添加组件至内容窗格

  ```
  Container contentPane = jframe.getContentPane();		//[1] 获取内容窗格对象
  contentPane.add(button, BorderLayout.CENTER);			//[2] 将组件添加到内容窗格JAVA
  ```

  > 1. 使用顶层容器的 `getContentPane()` 方法获得其内容窗格对象。
  >
  > 2. 将组件添加到内容窗格。
  >
  >    其中 button 是一个按钮控件，BorderLayout.CENTER 代表位置在中间
  >
  >    向顶层容器内容窗格添加组件时，也可以直接调用顶层容器的 add() 方法

* 以新的内容窗格代替原有内容窗格

  ```
  JPanel contentPane = new JPanel();					//[1] 创建 JPanel 实例
  contentPane.setLayout(new BorderLayout());			//[2] 创建布局管理器
  contentPane.add(button, BorderLayout.CENTER);		//[3] 添加组件
  jfream.setContentPane(contentPane);					//[4] 添加内容窗格JAVA
  ```

  > 1. 创建 JPanel（面板）实例。JPanel 是 Container 的子类。
  > 2. 顶层容器默认的布局管理器是 BorderLayout，而 JPanel 的默认布局管理器是 FlowLayout
  > 3. 通过 `setContentPane(contentPane)` 方法为 jfream 设置新的内容窗格

### 15.1.3 面板

> 普通面板（JPanel）和滚动面板（JScrollPanel）都是用途广泛的容器。面板与顶层容器不同，不能独立存在，而必须添加到其他容器内部。面板可以嵌套，由此可以设计出复杂的图形用户界面。

#### #15.1.3.1 JPanel

**构造方法：**

* `new JPanel()`：创建具有 FlowLayout 布局的新面板
* `new JPanel(LayoutManager layout)`：创建指定布局的新面板

**常用方法：**

* `Component add(Compinent comp)`：将指定组件添加至面板

#### #15.1.3.2 JScrollPanel

JScrollPanel 是一个带有滚动条的面板，但只能添加一个组件。添加多个组件的场合，先将那些组件添加入 JPanel 对象，再将该 JPanel 对象添加进 JScrollPanel 对象中

**构造方法：**

* `new JScrollPanel()`：创建一个空的 JScrollPanel。水平、垂直滚动条都能显示
* `new JScrollPanel(Component view)`：创建指定组件内容的 JScrollPanel

**常用方法：**

* `void setHorizontalScrollBarPolicy(int policy)`：确定水平滚动条何时显示

  其中 policy 的可选值是以下三种：

  * ScrollPaneConstants.HORIZONTAL\_SCROLLBAR\_AS\_NEEDED：需要时可见
  * ScrollPaneConstants.HORIZONTAL\_SCROLLBAR\_NEVER：永远不可见
  * ScrollPaneConstants.HORIZONTAL\_SCROLLBAR\_ALWAYS：永远可见

* `void setVerticalScrollBarPolicy(int policy)`：确定垂直滚动条何时显示

  其中 policy 的可选值是以下三种：

  * ScrollPaneConstants.VERTICAL\_SCROLLBAR\_AS\_NEEDED：需要时可见
  * ScrollPaneConstants.VERTICAL\_SCROLLBAR\_NEVER：永远不可见
  * ScrollPaneConstants.VERTICAL\_SCROLLBAR\_ALWAYS：永远可见

#### #15.1.3.3 Scrollbar

AWT 中还有一个滚动条组件，提供了允许用户在一定范围的值中进行选择的便捷方式

**构造器：**

* `new Scrollbar()`：构造一个新的滚动条

* `new Scrollbar(int orientation)`：构造一个有指定方向的滚动条

  其中 orientation 的可选值是：

  * Scrollbar.HORIZONTAL：水平滚动条
  * Scrollbar.VERTICAL：垂直滚动条

* `new Scrollbar(int orientation, int value, int visible, int minimum, int maximun)`：构造一个有初始方向、初始值、可视量、最小/最大值的滚动条

## 15.2 标签和按钮

### 15.2.1 标签

> 标签（JLable）是最简单的 Swing 组件，通常用于显示提示性的文本信息或图标。其不可被编辑

**构造器：**

* `new JLable()`：构造一个不显示文本或图标的空标签

* `new JLable(Icon image)`：构造一个显示图标的标签

* `new JLable(String text)`：构造一个显示文本的标签

* `new JLable(Icon image, int horizontalAlignment)`：构造一个显示图标的标签，并指定水平对齐方式

  其中 horizontalAlignment 的取值可以是：

  * JLable.LEFT：左对齐
  * JLable.RIGHT：右对齐
  * JLable.CENTER：居中对齐

* `new JLable(String text, int horizontalAlignment)`：构造一个显示文本的标签，并指定水平对齐方式

* `new JLable(String text, Icon image, int horizontalAlignment)`：构造一个同时显示文本和图标的标签，并指定水平对齐方式

### 15.2.2 按钮

> 按钮（JButton）是 Java 图形用户界面的基本组件之一。
>
> 经常用到的按钮有 4 种形式：按钮（JButton）、切换按钮（JToggleButton）、复选按钮（JCheckButton）、单选按钮（JRadioButton）
>
> 这些按钮类均是 AbstractButton 的子类或间接子类

#### #15.2.2.1 JButton

**构造器：**

* `new JButton()`：构造一个既没有文本也没有图标的按钮
* `new JButton(Icon icon)`：构造一个有图标的按钮
* `new JButton(String text)`：构造一个有文本的按钮
* `new JButton(String text, Icon icon)`：构造一个有文本也有图标的按钮

**常用方法：**

* `void setMnemonic(int mnemonic)`：设置当前按钮的键盘助记符

* `void setText(String text)`：设置按钮的文本

  `void setIcon(Icon icon)`：设置按钮图标

* `void setEnabled(boolean mod)`：是否启用按钮

* `String getText()`：返回按钮的文本

* `void setToolTipText()`：设置提示文本

* `void addActionListener(ActionListener l)`：为按钮添加事件监听程序

  `void removeActionListener(ActionListener l)`：移除事件监听程序

#### #15.2.2.2 JToggleButton、JCheckButton、JRadioButton

> JToggleButton 是有两种状态（选中状态、未选中状态）的按钮。通过点击切换这两种状态
>
> JCheckButton、JRadioButton 是 JToggleButton 的子类，构造方法与 JToggleButton 相同。

**构造器：**

* `new JToggleButton()`：构造一个没有文本也没有图标的切换按钮

* `new JToggleButton(Icon icon)`：构造一个有图标的切换按钮

  `new JToggleButton(Icon icon, boolean selected)`：构造一个有图标的切换按钮，设置其初始状态

* `new JToggleButton(String text)`：构造一个有文本的切换按钮

  `new JToggleButton(String text, boolean selected)`

* `new JToggleButton(String text, Icon icon)`：构造一个有文本也有图标的切换按钮

  `new JToggleButton(String text, Icon icon, boolean selected)`

**常用方法（除 JButton 方法外）：**

* `boolean isSeleced()`：获取按钮的当前状态

* `void addItemListener(ItemListener l)`：为按钮添加事件侦听程序

  `void removeItemListener(ItemListener l)`：移除事件侦听程序

## 15.3 布局管理器

> 容器中包含了组件。组件的布局，包括其位置和大小，通常由布局管理器负责安排。每个容器都有一个默认的布局管理器。

### 15.3.1 FlowLayout 流布局管理器

> FlowLayout 定义在 java.awt 包中。

**构造器：**

* `new FlowLayout()`：创建默认的 FlowLayout 布局管理器。

  居中对齐。默认水平、垂直间距是 5 像素

* `new FlowLayout(int align)`：创建一个 FlowLayout 布局管理器。

  指定对齐方式。默认水平、垂直间距是 5 像素

  align 的可选值有这些：

  * FlowLayout.LEFT：左对齐
  * FlowLayout.RIGHT：右对齐
  * FlowLayout.CENTER：居中对齐

* `new FlowLayout(int align, int hgap, int vgap)`：创建一个 FlowLayout 布局管理器。

  指定对齐方式。指定水平、垂直间距

**使用说明：**

1. FlowLayout 对容器中组件进行布局的方式是将组件逐个放置在容器中的一行上。一行满后，另起一行。
2. 与其他布局管理器不同。FlowLayout 不会强行设定组件的大小，而是通过每个组件的 getPreferredSize() 方法获取其各自的期望大小

### 15.3.2 BorderLayout 边界布局管理器

> BorderLayout 定义在 java.awt 包中，是顶层容器的默认布局管理器。它提供了一种较为复杂的组件布局管理方案。

**构造方法：**

* `new BorderLayout()`：一个组件间没有间距的布局管理器
* `new BorderLayout(int hgap, int vgap)`：定义水平、垂直间距的布局管理器

**使用说明：**

1. 每个由 BorderLayout 管理的容器被划分成 5 个区域，分别是容器的 上部（BorderLayout.NORTH）、下部（BorderLayout.SOUTH）、左部（BorderLayout.WEST）、右部（BorderLayout.EAST）、中部（BorderLayout.CENTER）
2. 在 BorderLayout 布局管理器的管理下，组件必须通过 add() 方法加入容器的指定区域。不指定的场合，默认加入中部区域
3. 容器的每个区域仅能加入一个组件。加入了多个组件的场合，只有最后一个生效
4. 对于中部以外的 4 个边界区域，没有使用的区域的大小将变为零。此时，中部区域会扩展并占据该未用区域的位置。如果 4 个边界区域都没有使用，中部区域会占据整个窗口。
5. 窗口大小改变时，按钮的相对位置不会变化。但按钮的大小会改变

### 15.3.3 GridLayout 网格布局管理器

> GridLayout 定义在 java.awt 包中，是一种网格式的布局管理器。

**构造器：**

* `new GridLayout()`：创建一个一行的网格。列数根据实际需要决定

* `new GridLayout(int rows, int cols)`：指定行数、列数的网格布局

  rows 和 cols 中，最多有一个可以是 0，但不能都为 0。那个为 0 的场合，根据实际需要决定数量

* `new GridLayout(int rows, int cols, int hgap, int vgap)`：指定行数、列数，也指定水平、垂直间距的网格

**使用说明：**

1. GridLayout 将容器空间划分成若干行乘若干列的网格。组件依次放入其中，每个组件占据一格

2. 网格每列宽度相同，每行高度相同。组件放入的次序决定了其位置

3. 想要组件间留有空白的场合，可以添加一个空白标签

4. 网格数多于组件数的场合，那些多余的格子为空白。

   网格数少于组件数的场合，系统根据需要适当添加。

5. 窗口大小改变时，按钮的相对位置不会变化。但按钮的大小会改变

### 15.3.4 CardLayout 卡片式布局管理器

> CardLayout 定义在 java.awt 包中，是一种卡片式的布局管理器。

**构造器：**

* `new CardLayout()`：一个默认无间距的 CardLayout 布局管理器
* `new CardLayout(int hgap, int vgap)`：指定水平、垂直间距的 CardLayout 布局管理器

**常用方法：**

* `void first(Container parent)`：翻转到容器的第一张卡片

  `void next(Container parent)`：翻转到下一张卡片。当前是最后一张的场合，翻到第一张卡片

  `void previous(Container parent)`：翻转到前一张卡片。当前是第一张的场合，翻到最后一张卡片

  `void last(Container parent)`：翻转到容器的最后一张卡片

* `void show(Container parent, String name)`：翻转到卡片集中具有指定 name 的卡片。没有该卡片的场合不执行任何操作

**使用说明：**

1. CardLayout 将容器中的组件处理为一系列卡片，每一刻只显示其中一张。
2. 容器第一次显示时，显示第一个添加的组件。

### 15.3.5 BoxLayout 方框布局管理器

> BoxLayout 定义在 javax.swing 包中，其组件横排一行或竖排一列，但宽度（高度）可以不同。

**构造器：**

* `new BoxLayout(Container target, int axis)`：创建一个沿给定轴放置组件的布局管理器

  其中 target 表明为哪个容器设置此布局管理器，axis 指明组件排列方向

  axis 的值是：

  * BoxLayout.X\_AXIS：水平方向排列
  * BoxLayout.Y\_AXIS：竖直方向排列

  举个例子：

  > ```
  > JPanel jp = new JPanel();
  > jp.setLayout(new BoxLayout(jp, BoxLayout.X_AXXIS));JAVA
  > ```

**使用说明：**

1. BoxLayout 将容器中的组件横排一行或竖排一列。那些组件横排一行时，可以有不同宽度；竖排一列时，可以有不同的高度。
2. 窗口大小改变时，按钮的相对位置不会变化。

**Box 类**

> javax.swing 中定义了一个专门使用 BoxLayout 的容器 Box 类。

* `static Box createHorizontalBox()`：返回一个使用水平方向的 BoxLayout 的 Box

  `static Box createVerticalBox()`：返回一个使用竖直方向的 BoxLayout 的 Box

* `static Component createHorizontalGlue()`：创建一个水平方向的不可视的组件（填满剩余空间）

  `static Component createVerticalGlue()`

* `static Component createHorizontalStrut()`：创建一个水平方向的不可视的组件（指定宽高）

  `static Component createVerticalStrut()`

* `static Component createRigidArea()`：创建一个不可视的组件，不大不小，尺寸真是好极了

### 15.3.6 空布局

> 其实也能不用布局管理器的，真的

1. 调用容器的 `setLayout(null)` 将容器的布局管理器置空
2. 调用组件的 `setBounds(x, y, w, h)` 方法设置其位置和大小。~老东西，你的布局管理器最没用啦！~

## 15.4 事件处理

> Java 运行时，如果用户进行某个操作，程序应当做出相应。
>
> 程 · 序 · 无 · 响 · 应（噔 噔 咚）
>
> ……总之，用户在程序界面进行的操作称为用户事件，对事件的相应称为事件处理。

### 15.4.1 事件处理模型

> Java 事件处理是采取 “委派事件模型”。==当事件发生时，产生事件类对象。==这里说的事件类对象实际上就是 `java.awt.event` 事件类库里某个类创建的对象。操作不同的场合，事件类对象不同。
>
> 会把此事件类对象传递给事件侦听程序处理。它是实现了对应侦听程序接口的一个类。
>
> 事件只是一个对象，其只向注册的侦听程序报告

示范一个简单的事件处理：

```java
import javax.seing.*;
import java.awt.*;													//[0]

public class Code_15_4_1 {
    public static void main(String[] args) throws InterruptedException {
        Button b = new Button("Button");
        b.addActionListener((e) -> System.out.println("Pressed"));	//[1]
        JFrame jFrame = new JFrame("Test");
        jFrame.setBounds(30, 30, 100, 100);
        jFrame.add(b);
        jFrame.setVisible(true);
    }
}
```

> 1. 引入 java.awt.event 包
>
> 2. 注册事件侦听程序，并实现其方法
>
>    示例中的简写其实相当于以下代码（匿名内部类）：
>
>    ```java
>    b.addActionListener(new ActionListener() {
>        @OverRide
>        public void actionPerformed (ActionEvent e) {
>            System.out.println("Pressed");
>        }
>    });
>    ```
>
>    ***—— 示例写法见 \[27 Java8 新特性]***
>
>    事件侦听程序可以定义在单独的类中，也可以定义在组件类中：
>
>    ```java
>    class MyButton extends JButton implements ActionListener{
>        public MyButton() {
>            addActionListener(this);
>        }
>
>        @Override
>        public void actionPerformed(ActionEvent e) {
>            System.out.println("Pressed");
>        }
>    }
>    ```

### 15.4.2 事件的种类

> 在 java.awt.event 包和 javax.swing.event 包中定义了很多其他的事件类。每种事件类有一个对应的接口，接口中声明了一个或多个抽象的事件处理方法。

| 事件类           | 组件                                                   | 方法及说明                                                                                                                                                                                                                    |
| ---------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ActionEvent      | JButton、JCheckBox、JComboBox、JMenuItem、JRaidoButton | actionPerformed(e) 单击按钮、选择菜单项或在文本框中按回车时                                                                                                                                                                   |
| AdjustmentEvent  | JScrollBar                                             | adjustmentValueChanged(e) 当改变滚动条滑块位置时                                                                                                                                                                              |
| ComponentEvent   | JComponent 及其子类                                    | componentMoved(e) 组件移动时；componentHidden(e) 组件隐藏时；componenetResized(e) 组件缩时；componentShown(e) 组件显示时                                                                                                      |
| ContainerEvent   | JContainer 及其子类                                    | containerAdded(e) 添加组件时；containerMoved(e)移除组件时                                                                                                                                                                     |
| FocusEvent       | 同 ComponentEvent                                      | focusGained(e) 组件获得焦点时；focusLost(e) 组件失去焦点时                                                                                                                                                                    |
| ItemEvent        | JCheckBox、JCheckboxMenuItem、JComboBox                | itemStateChanged(e) 选择复选框、选项框、单击列表框、选中带复选框菜单时                                                                                                                                                        |
| KeyEvent         | 同 ComponentEvent                                      | keyPressed(e) 键按下时；keyReleased(e) 键释放时；keyTaped(e) 击键时                                                                                                                                                           |
| MouseEvent       | 同 ComponentEvent                                      | mousePressed(e) 鼠标按下时；mouseEntered(e) 鼠标进入时；mouseExited(e) 鼠标离开时；mouseClicked(e) 鼠标点击时；mouseReleased(e) 鼠标释放时                                                                                    |
| MouseMotionEvent | 同 ComponentEvent                                      | mouseDragged(e) 鼠标拖放时；mouseMoved(e) 鼠标移动时                                                                                                                                                                          |
| TextEvent        | JTextField、JTextArea                                  | textValueChanged(e) 文本框、多行文本框内容修改时                                                                                                                                                                              |
| WindowEvent      | JFrame、JWindow、JDialog                               | windowOpened(e) 窗口打开时；windowClosing(e) 窗口关闭时；windowClosed(e) 窗口关闭后；windowActived(e) 窗口激活时；windowDeactivated(e) 窗口失去焦点时；windowIconified(e) 窗口最小化时；WindowDeiconified(e) 窗口最小化还原时 |

使用说明：

1. XXXEvent 事件类的接口名就是 XXXListener。占地方太大，上面就没写

2. 事件侦听模式允许为一个组件多次调用 addListener 方法，注册多个侦听程序。

   事件发生时，单个事件的多个侦听程序的调用顺序不确定。

3. 调用事件处理方法时会传入一个参数。该参数就是那些事件类实例，其中包含事件有关的重要信息。

   调用这些事件类实例各自特有的方法获取这些信息。

### 15.4.3 事件适配器

> 侦听程序必须实现接口的全部抽象方法，但有时我们只关心其中的某一种方法。为了编程方便，Java 为一些声明了多个抽象方法的 Listener 接口提供了相应的适配器类。

| 接口名称            | 适配器名称         |
| ------------------- | ------------------ |
| ComponentListener   | ComponentAdapter   |
| ContainerListener   | ContainerAdapter   |
| FocusListener       | FocusAdapter       |
| KeyListener         | KeyAdapter         |
| MouseListener       | MouseAdapter       |
| MouseMotionListener | MouseMotionAdapter |
| MouseInputListener  | MouseInputAdapter  |
| WindowListener      | WindowAdapter      |

这些适配器实现了接口的全部抽象方法，只是方法内容为空

这样，创建新类时可以不必实现接口，而是继承适配器类，并重写需要的方法

## 15.5 Java 绘图技术

> Java 语言约定，显示屏上一个长方形区域为程序绘图区域，坐标原点（0, 0）位于整个区域左上角。
>
> 一个坐标点（x, y）对应一个像素，其中坐标点的 x、y 必须是非负整数。x 沿水平方向从左向右递增、y 沿竖直方向从上往下递增

### 15.5.1 颜色

> 可以使用 java.awt 包中的 Color 类来定义和管理颜色。Color 类的每个对象代表一种颜色。

Color 类有 26 个常量，代表 13 种预定义颜色：

| 颜色              | 对象            | ᓚᘏᗢ             |
| ----------------- | --------------- | --------------- |
| 黑色（#000000）   | Color.black     | Color.BLACK     |
| 蓝色（#0000FF）   | Color.blue      | Color.BLUE      |
| 青色（#00FFFF）   | Color.cyan      | Color.CYAN      |
| 灰色（#808080）   | Color.gray      | Color.GRAY      |
| 深灰色（#404040） | Color.darkGary  | Color.DARKGARY  |
| 浅灰色（#C0C0C0） | Color.lightGary | Color.LIGHTGARY |
| 绿色（#00FF00）   | Color.green     | Color.GREEN     |
| 洋红色（#FF00FF） | Color.magenta   | Color.MAGENTA   |
| 橙色（#FFC800）   | Color.orange    | Color.ORANGE    |
| 粉红色（#FFAFAF） | Color.pink      | Color.PINK      |
| 红色（#FF0000）   | Color.red       | Color.RED       |
| 白色（#FFFFFF）   | Color.write     | Color.WRITE     |
| 黄色（#FFFF00）   | Color.yellow    | Color.YELLOW    |

也可以通过红、绿、蓝三原色值来组合。每种颜色由三个值组成（RGB），值的范围是 \[0, 256)：

```java
Color color = new Color(102, 204, 255);
```

> 这个颜色是：天依蓝（#66CCFF）
>
> 这部分笔记恐怕是所有 Java 笔记里唯一有五颜六色的地方了，真难得

### *15.5.2 字体*

> 文字有 字体、样式、字号 三个要素

基本的样式有：

* Font.PLAIN：正常（0）

* Font.BOLD：**粗体**（1）

* Font.ITALIC：*斜体*（2）

  样式可以组合使用，如 (Font.BOLD + Font.ITALIC) 即 ***粗斜体***

常用的字体：Times New roman（Times New roman）、Symbol（Symbol）、宋体（宋体）、楷体（楷体）等

**构造器：**

* `new Font(String name, int style, int size)`：新建字体，指定字体、样式、字号

**常用方法：**

* `String getName()`：返回其字体名称

  `int getSize()`：返回其字号大小

  `int getStyle()`：返回其样式

* `boolean isBold()`：是否是粗体

  `boolean isItalic()`、`boolean isPlain()`

### 15.5.3 Graphics 类

> java.awt 包下的 Graohics 类是所有图形处理的基础，是所有图形上下文的抽象父类。其允许应用程序在组件及屏幕图像上进行绘制。

当先后绘制的图形不同时，确定重叠部分颜色的方法称为绘图模式。

绘图模式分为两种：

* 正常模式：`setPaintMode()`

  后绘制的图形覆盖先绘制的图形。

* 异或模式：`setXORMode(Color c)`

  当前绘制的颜色、先前绘制的颜色 及 选定的颜色 c 之间进行某种处理后，用新的颜色绘制。

  这个模式下，如果使用同一颜色绘制 2 次，则相当于擦除之前绘制的图形，即恢复原本状态。

若要在某个组件中绘图，应重写该组件的 paint(Graphics g) 方法，并在重写的方法内进行绘图。

* `paint(Graphics g)`：组件被显示出来时，调用该方法

  以下情况该方法会被调用

  * 组件第一次在屏幕显示时
  * 窗口最小化/最大化
  * 窗口大小发生变化
  * `repaint` 函数被调用

* `repaint()`：重绘组件

  该方法默认情况下会调用以下方法

  * `paintCompnent(Graphics g)`：绘制组件

    一般情况下，要在 JComponent 子类的组件中绘图，重写该方法即可

  * `paintBorder(Graphics g)`：绘制组件边框

  * `paintChildren(Graphics g)`：绘制组件的子组件

**设置画笔：**

* `setColor(Color c)`：设置画笔前景色

  `setBackgroud(Color c)`：设置背景色

  每个图形环境都有一种画图时正在使用的前景色。画图所在的每个表面都有背景色

* `setFont(Font f)`：设置画笔字体

**绘制几何图案：**

* `drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)`：绘制弧线

  该弧线是 沿着起始点为 (x, y)、宽 width、高 height 的矩形所限定的椭圆 绘制一条弧线。

  该弧线起始角度为 startAngle，弧度角度为 arcAngle

  `fillArc(int x, int y, int width, int height, int startAngle, int arcAngle)`：填充扇形

* `drawLine(int x1, int y1, int x2, int y2)`：绘制线段

  该线段的两个端点是 (x1, y1) 和 (x2, y2)

* `drawOval(int x, int y, int width, int height)`：绘制椭圆

  一个起始点为 (x, y)、宽 width、高 height 的矩形所限定的椭圆

  `fillOval(int x, int y, int width, int height)`：填充椭圆

* `drawPolygon(int[] xPoints, int[] tPoints, int nPoints)`：绘制多边形

  `drawPolygon(Polygon p)`：也能通过传入一个 Polygon 对象来绘制多边形

  该图形的端点由传入参数确定。如果最后一个端点和第一个端点不相等，那么图形可能不闭合

  `fillPolygon(int[] xPoints, int[] tPoints, int nPoints)`：填充多边形

  `fillPolygon(Polygon p)`：通过传入一个 Polygon 对象来填充多边形

  低情商：这咋填充？高情商：留给读者们思考吧

* `drawRect(int x, int y, int width, int height)`：绘制矩形

  一个起始点为 (x, y)、宽 width、高 height 的矩形

  `fillRect(int x, int y, int width, int height)`：填充矩形

* `drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)`：圆角矩形

  一个起始点为 (x, y)、宽 width、高 height 的矩形，其圆角形状由 arcWidth、arcHeight 确定

  其实就是矩形（线段）和椭圆（圆弧）的某种拼接

  `fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)`：填充圆角矩形

* `drawString(String str, int x, int y)`：输出字符串

  在 (x, y) 处输出字符串 str，向右扩展

  `drawChars(char[] chars, int offSet, int length, int x, int y)`：输出字符串

  从字符数组的 offSet 下标处起，在 (x, y) 处输出 length 个字符，向右扩展

  `drawBytes(byte[] data, int offSet, int length, int x, int y)`：还不是一样？

* `draw3DRect(int x, int y, int width, int height, boolean raised)`：绘制 3D 矩形

  `fill3DRect(int x, int y, int width, int height, boolean raised)`：填充 3D 矩形

  我感觉是和 PS 里的浮雕效果类似，生成了亮部和暗部。rasied 决定是凸起还是凹陷。

#### #15.5.3.1 Polygon 类

> Polygon 对象封装了多边形的坐标。

构造方法：

* `new Polygon()`：构造一个空的多边形。

  无为太虚，无生一，一生二，二生三，三生万物。所以，空的多边形也是多边形。

* `new Polygon(int[] xs, int[] ys, int n)`：构造一个 n 点连接成的多边形

常用方法：

* `addPoint(int x, int y)`：将该点加入多边形中

* `boolean contains(int x, int y)`：如果多边形包含该点，则返回真

  `boolean contains(Point p)`

* `get Bounds()`：得到多边形的外接矩形

* `translate(int dx, int dy)`：偏移

  将多边形各点沿 x 轴偏移 dx，再沿 y 轴偏移 dy

### 15.5.4 Graphics2D 类

> 为了解决图形对象的局限性，Java 1.1 后 引入了 Java 2D，其包含一个继承自 Graphics 的 Graphics2D 类，增加了很多状态属性，扩展了 Java 的绘图功能
>
> Graphics2D 拥有强大的二维图形处理能力，提供对几何形状、坐标转换、颜色管理、文字布局等更复杂的控制

#### #15.5.4.1 图形状态属性

> 通过设定和修改属性，可以指定画笔宽度和画笔连接方式、设定平移、旋转、缩放或裁剪变换图形，还能设置填充图形的颜色和图案等。

**状态属性：**

* **stroke 属性：**

  该属性控制线宽、笔形样式、线段连接方式、短划线图案。

  使用 `setStroke(Stroke s)` 方法设置 stroke 属性。

  构造方法：

  * `new BasicStroke(float w)`：指定线宽的 stroke

  * `new BasicStroke(float w, int cap, int join)`：指定线宽、端点样式 cap、线段交汇方式 join 的 stroke

    其中，端点样式 cap 的值可以是：

    * BasicStroke.CAP\_BUTT：无修饰（0）
    * BasicStroke.CAP\_ROUND：半圆形末端（1）
    * BasicStroke.CAP\_SQUARE：方形末端（2，默认值）

    线段交汇方式 join 的值可以是：

    * BasicStroke.JOIN\_BEVEL：无修饰（2）
    * BasicStroke.JOIN\_MITER：尖型末端（0，默认值）
    * BasicStroke.JOIN\_ROUND：圆形末端（1）

* **paint 属性：**

  该属性控制填充效果。

  使用 `setPaint(Paint p)` 方法设置 paint 属性

  构造方法：

  * `new GradientPaint(float x1, float y1, Color c1, float x2, float y2, Color c2)`：构造一个简单的非周期性的 paint 对象。

    从点 (x1, y1) 至点 (x2, y2) 处，颜色由 c1 渐变至 c2

  * `new GradientPaint(float x1, float y1, Color c1, float x2, float y2, Color c2, boolean cyclic)`：构造一个周期或非周期性的 paint 对象。

    如果希望渐变至终点又是起点的颜色，将 cyclic 设定为 true

* **transform 属性：**

  该属性用来实现常用的图形平移、缩放、斜切等

  使用 `setTransform(Transform t)` 方法设置 transform 属性

  构造方法：

  * `new AffineTransform()`：构造一个表示仿射变换的新的 tansform

    *仿射变换：用我的话来讲，就是保持坐标点不变的情况下变换坐标系，能形成的变换*

  常用方法：

  * `setToRotation(double theta)`：旋转 theta 角度

    `setToRotation(double theta, double x, double y)`：以 (x, y) 为旋转中心旋转

    `rotate(double theta, double x, double y)`

  * `setToScale(double sx, double sy)`：拉伸变换

    x、y 方向按照 sx、yx 比例变换

    `scale(double sx, double sy)`

  * `setToTranslation(double tx, double ty)`：平移变换

    `translate(double tx, double ty)`

  * `setToShear(double shx, double shy)`：斜切变换

    shx、shy 分别指定 x、y 方向的斜拉度

  看得出来，出版社的人写到这里应该是到了下班的点了。内容不明不白的不说，还写错了好几处。

  我笔记上是改过来的。放心。

  就这还 ”指定教材“ 呢。垃圾，还没我笔记写得好。

* **clip 属性：**

  该属性用于实现剪裁效果。

  使用 `setClip(Shape clip)` 方法，确定裁剪区域的 Shape。可以连续使用该方法以得到其交集区域

* **composit 属性：**

  该属性设置图形重叠区域的效果

  通过 `Alpha.Composite.getInstance(int rule, float alpha)` 得到一个实例。其中 alpha（透明度）的范围是 \[0.0f, 1.0f]

  至于 rule 是什么……我也不知道。或许不重要吧

  通过 `setComposite(Composite comp)` 设置混合效果

#### #15.5.4.2 Graphics2D 的绘图方法

> Graphics2D 保留了 Graphics 的绘图方法，又增加了很多新方法
>
> 另外，在 java.awt.geom 中声明了一系列类，能用于创建各种几何图形对象，包括：Line2D 线段类、Rectangle2D 矩形类、RoundRectangle2D 圆角矩形类、Ellipse2D 椭圆类、Arc2D 圆弧类、QuadCurve2D 二次曲线类、CubicCurve2D 三次曲线类
>
> 这些类都是抽象类，但这些类包中有实现了其方法的 Double 类。

```java
...
@OverRide
public void paintComponents(Graphics g) {
    Graphics2D g2d = (Graphics2D)g;						//[1]
    Line2D line = new Line2D.Double(5, 5, 5, 5);		//[2]
    g2d.draw(line);										//[3]
}
```

> 1. 将 Graphics 对象强制转型为 Graphics2D 对象
>
> 2. 创建几何图形对象
>
>    这里的 Line2D.Double 表示 Line2D 包下的 Double 类。此处调用了 Double 类的构造器
>
> 3. 绘制线段

#### #15.5.4.3 Graphics2D 中的几何图形类

* `new Line2D.Double(int x1, int y1, int x2, int y2)`：线段

  从 (x1, y1) 到 (x2, y2) 的线段

* ……（略了略了。相信椭圆、矩形之类的也不用再详细说明了吧）

* `new Arc2D.Double(double x, double y, double w, double h, double start, double extent, int type)`：弧

  但是特别地，type 的值是以下几种：

  * Arc2D.OPEN：开弧
  * Arc2D.CHORD：弓弧
  * Arc2D.PIE：饼弧

* `new QuadCurver2D.Double(double x1, double y1, double ctrlx, double ctrly, double x2, double y2)`：绘制二次曲线

  绘制二次曲线需要 3 个点，分别是起始点 (x1, y1)、终点 (x2, y2)、控制点 (ctrlx, ctrly)

* `new CubicCurve2D.Double(double x1, double y1, double ctrlx1, double ctrly1, double ctrlx2, double ctrly2, double x2, double y2)`：绘制三次曲线

  绘制三次曲线需要 4 个点，分别是起始点、终点、两个控制点

## 15.6 组合框与列表

### 15.6.1 JComboBox 组合框

> JComboBox 是一个下拉式菜单。它有两种模式：可编辑的、不可编辑的。
>
> 对于不可编辑的 JComboBox，用户只能在现有列表中进行选择
>
> 对于可编辑的 JComboBox，用户既能在现有选项中选择，也能输入新的内容

**构造方法：**

* `new JComboBox<E>()`：创建一个 E 类型（泛型）的没有任何可选项的默认组合框

* `new JComboBox<E>(E[] items)`：根据 items 数组创建组合框

  items 数组元素即为组合框的可选项

**常用方法：**

* `void setEditable(boolean aFlag)`：设置可编辑性

  不设置的场合，默认处于不可编辑状态

* `void addItem(E item)`：在末尾添加可选项

  `void insertItemAt(E item, int index)`：在指定下标处添加可选项

* `void removeAllItems()`：删除所有可选项

  `void removeItem(E iten)`：删除 item 指定的可选项

  `void removeItemAt(int index)`：删除指定下标处的可选项

* `E getItemAt(int index)`：获取指定下标的可选项

* `int getItemCount()`：获取列表项数

* `int getSelectedIndex()`：获取选中项匹配的第一个选项的索引下标

  `E getSelectedItem()`：获取选中项

### 15.6.2 JList 列表

> JList 是可供用户选择的一系列可选项

**构造方法：**

* `new JList<E>()`：构造一个空列表
* `new JList<E>(E[] listData)`：构造一个列表，可选元素由 listData 指定
* `new JList<E>(Vector<E> listData)`：构造一个列表，使其显示指定 Vector 中的元素

**常用方法：**

* `addListSelectionListener(ListSelectionListener l)`：添加监视器

  当用户在列表上选择时，会触发 ListSelectionEvent 事件。

  在 ListSelectionListener 接口中，仅有一个方法：

  ```java
  void valueChanged(ListSelectionEvent e);
  ```

  当列表的当前选项改变时，会调用该方法。

* `int getSelectedIndex()`：返回选中项第一次出现的下标索引

  没有选中项的场合，返回 -1

  `E getSelectedValue()`：返回所选的第一个值

  没有选中项的场合，返回 null

* `void setVisableRowCount(int count)`：设置不使用滚动条可以在列表中显示的选项行数

* `void setSelectionMode(int mode)`：设置列表的选择模式

  其中，mode 的值可以是以下几种：

  * ListSelectionModel.SINGLE\_SELECTION：仅支持单项选择（0）
  * ListSelectionModel.SINGLE\_INTERVAL\_SELECTION：可多选，但多个选项必须是连续的（1）
  * ListSelectionModel.MULTIPLE\_INTERVAL\_SELECTION：可多选（2，默认值）

## 15.7 文本组件

> 文本组件可以提示信息和提供用户输入功能。Swing 中提供了 JTextField（文本域）、JPasswordField（口令输入域）、JTextArea（文本区）等多个文本组件。

文本组件的共同的父类是 JTextComponent，其中定义了文本组件的共有方法：

* `String getSelectedText()`：从文本组件中提取被选中的文本内容

* `String getText()`：从文本组件中提取全部文本内容

  `String getText(int offs, int len)`：从文本组件中提取指定范围的文本内容

* `void select(inr start, int end)`：在文本组件中选中指定范围内容

  `void selectAll()`：在文本组件中选中全部内容

* `void setEditable(boolean b)`：设置可编辑状态

* `void setText(String t)`：设置文本组件的文本内容

* `void setDocument(Document doc)`：设置文本组件的文档

* `void copy()`：复制选中文本至剪贴板

  `void cut()`：剪切选中文本至剪贴板

  `void paste()`：粘贴剪贴板内容至当前位置

另外，JComponrnt 类中有如下方法：

* `boolean requestFocusInWindow()`：请求当前组件获得输入焦点

### 15.7.1 JTextField 文本域

> 文本域是一个单行的文本输入框，可以用于输入少量文本

**构造方法：**

* `new JTextField()`：构造一个空文本域

* `new JTextField(int columns)`：构造一个指定列数的空文本域

  由于组件大小通常由布局管理器确定，因此指定的列数可能被忽略

* `new JTextField(String text)`：构造一个显示指定初始字符的文本域

* `new JTextField(String text, int columns)`：构造一个指定列数及初始字符的文本域

**常用方法：**

* `void addActionListener(ActionListener l)`：添加指定侦听器

  `void removeActionListener(ActionListener l)`：移除指定侦听器

* `void setFont()`：设置当前字体

* `void setHorizontalAlignment(int alignment)`：设置水平对齐方式

  有效值包括：

  * JTextField.LEFT：左对齐（2）

  * JTextField.RIGHT：右对齐（4）

  * JTextField.CENTER：居中对齐（0）

  * JTextField.LEADING：领先对齐（10）

    （似乎是）识别文字前端的对齐方式……好吧，我也不太明白

  * JTextField.TRAILING：落后对齐（11）

* `int getColumns()`：返回文本域列数

### 15.7.2 JTextArea 文本区

> JTextArea 是一个多行多列的文本输入框

**构造方法：**

* `new JTextArea()`：构造一个空文本区
* `new JTextArea(int rows, int columns)`：构造一个指定行数、列数数的空文本区
* `new JTextArea(String text)`：构造一个显示指定初始字符的文本区
* `new JTextArea(String text, int rows, int columns)`：构造一个指定行列数及初始字符的文本区

**常用方法：**

* `void append(String str)`：追加文本到文本区

* `void insert(String str, int pos)`：将指定文本插入到特定位置 pos 处

* `void replaceRange(String str, int start, int end)`：用指定文本 str 替换指定范围的文本

* `void addAncestorListener(AncestorListener listener)`：添加指定侦听器

  特别地，用户输入文本时，按下 Enter 键的场合只是向缓冲区输入一个字符，而不能表示输入的结束。因此，需要识别用户输入完成时，通常要在文本区旁放置一个确定按钮

## 15.8 菜单组件

> 菜单是最常用的 GUI 组件之一。Swing 包中提供了多种菜单组件。
>
> 菜单有下拉式菜单和弹出式菜单两种

### 15.8.1 菜单栏及菜单

> JMenuBar 菜单栏是窗口主菜单，用来包容一组菜单
>
> 通过容器的 `setJMenuBar(JMenuBar m)` 方法将菜单栏放到窗口上

**菜单栏 JMenuBar 构造方法：**

* `new JMenuBar()`：构建一个新的菜单栏

菜单栏也能注册一些事件侦听程序，但通常情况下对于这些事件我们都不进行处理

**菜单构造方法：**

* `new JMenu()`：构造没有文本的新菜单
* `new JMenu(String str)`：构造有指定标签的菜单
* `new JMenu(String str, boolean b)`：构造有指定标签的菜单，并指示其是否可以被分离

**常用方法：**

* `add(JMenu m)`：菜单可以被加入菜单栏或另一个菜单中

* `addSeparator()`：为菜单各项间加入间隔线

  `insertSeparator(int index)`：在指定位置插入间隔线

  也能通过这种方式插入间隔线：

  ```java
  menu.add(new JSeparator());				//JSeparator 类即分割线类
  ```

### 15.8.2 JMenuItem 菜单项

> JMenuItem 菜单项是菜单系统的最下一级

**构造方法：**

* `new JMenuItem()`：创建不带有设置文本或图标的菜单项

* `new JMenuItem(Icon i)`：创建只有图标的菜单项

* `new JMenuItem(String str)`：创建只有文本的菜单项

* `new JMenuItem(String str, Icon i)`：创建有图标和文本的菜单项

* `new JMenuItem(String str, int mnemonic)`：创建有文本和快捷键的菜单项

  那个快捷键的有效值是 KeyEvent 包下的各种常量

**常用方法：**

* `setMnemonic(int m)`：设置快捷键

* `setAccelerator(KeyStroke keyStroke)`：设置加速键

* `addActionListener(ActionListener l)`：加入侦听器

  菜单项被选中时，会触发 ActionEvent 事件

### 15.8.3 复选菜单项和单选菜单项

> JCheckBoxMenuItem（复选菜单项）和 JRadioButtonMenuItem（单选菜单项）是两种特殊的菜单项。
>
> JCheckBoxMenuItem 前有个小方框，以供提示复选
>
> JRadioButtonMenuItem 前有个小圆圈，以供提示单选

**复选菜单项构造器：**

* `new JCheckBoxMenuItem()`：一个无文本图标，初始未选中的菜单项
* `new JCheckBoxMenuItem(Icon i)`：就是有图标咯
* `new JCheckBoxMenuItem(String str)`：就是有文本咯
* `new JCheckBoxMenuItem(String str, boolean b)`：就是有文本，又指定初始状态咯
* `new JCheckBoxMenuItem(String str, Icon i)`：就是有文本，又有图标咯
* `new JCheckBoxMenuItem(String str, Icon i, boolean b)`：你猜呗

**单选菜单项构造器：**

单选菜单项是 JRadioButtonMenuItem，剩下的都和上面的一样

## 15.9 对话框

> 对话框是一个临时的可移动窗口，其依赖于其他窗口。当期依赖的窗口消失或最小化时，对话框消失。窗口还原时对话框恢复。
>
> 对话框分为强制型和非强制型。强制型对话框在关闭前，其他窗口不能接收任何形式的输入。也就是说，该对话过程不能中断。强制型对话框也被称为模式窗口。

### 15.9.1 JDialog 自定义对话框

**构造方法：**

* `new JDialog(Dialog owner)`：无标题无模式对话框。指定对话框为其所有者

  `new JDialog(Frame owner)`：指定框架为其所有者

* `new JDialog(Dialog owner, boolean model)`：无标题对话框，指定所有者并指示是否为有模式

* `new JDialog(Dialog owner, String title)`

  `new JDialog(Frame owner, String title)`

  `new JDialog(Dialog owner, String title, boolean model)`

  `new JDialog(Frame owner, String title, boolean model)`：这些你都懂的吧

### 15.9.2 JOptionalPane 标准对话框

> JDialog 通常用于创建自定义对话框。此外，还有用于显示标准对话框的 JOptionalPane 类

JOptionalPane 定义了多个**静态方法**，分为以下 4 个类型：

* **showConfirmDialog**：确认对话框。显示问题，要求用户进行确认（yes / no / cancel）

  其同名方法有以下 3 种

  * int showConfirmDialog(Component parentComponent, Object message)
  * int showConfirmDialog(Component parentComponent, Object message, String title, int optionType)
  * int showConfirmDialog(Component parentComponent, Object message, String title, int optionType, int messageType)

* **showInputDialog**：输入对话框。提示用户进行输入

  同名方法有以下 6 种：

  * showInputDialog(Object message)
  * showInputDialog(Object message, Object initialValue)
  * showInputDialog(Component parentComponent, Object message)
  * showInputDialog(Component parentComponent, Object message, Object initialValue)
  * showInputDialog(Component parentComponent, Object message, String title, int messageType)
  * showInputDialog(Component parentComponent, Object message, String title, int messageType, Icon icon, Object\[] options, Object initialValue)

* **showMessageDialog**：信息对话框。显示信息

  同名方法有以下 3 种：

  * showMessageDialog(Component parentComponent, Object message)
  * showMessageDialog(Component parentComponent, Object message, String title, int messageType)
  * showMessageDialog(Component parentComponent, Object message, String title, int messageType, Icon icon)

* **showOptionDialog**：选项对话框，显示选项，要求用户进行选择

  其只有 1 种同名方法

  * showOptionDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object\[] options, Object initialValue)

以上各个方法的参数分别是：

* Component parentComponent：对话框的父窗口对象。其父窗口位置决定了对话框位置

  该值可以是 null，表示用默认的 Frame 作为父窗口。这个场合，对话框位置在屏幕正中

* Object message：显示在对话框的描述信息。

  该参数通常是 String 对象，但也能是一个图标、组件或对象数组

* String title：对话框标题

* int optionType：对话框上的按钮类型。可以是以下常量：

  * JOptionalPane.DEFAULT\_OPTION：默认（-1）
  * JOptionalPane.YES\_NO\_OPTION：一组 yes / no（0）
  * JOptionalPane.YES\_NO\_CANCEL\_OPTION：一组 yes / no / cancel（1）
  * JOptionalPane.OK\_CANCEL\_OPTION：一组 ok / cancel（2）

  此外，也能通过 Object\[] options 参数指定其他形式

* Object\[] options：对话框上的选项。

  在输入对话框中，通常以组合框架形式显示。在选项对话框中，则是指按钮类型。

  该参数通常是一个 String\[] 数组，但也能是图标数组或组件数组

* int messageType：对话框传递的信息类型。可以是以下常量：

  * JOptionalPane.ERROR\_MESSAGE：错误信息（0）
  * JOptionalPane.INFORMATION\_MESSAGE：普通信息（1）
  * JOptionalPane.WARNING\_MESSAGE：警告信息（2）
  * JOptionalPane.QUESTION\_MESSAGE：提问信息（3）
  * JOptionalPane.PLAIN\_MESSAGE：无格式信息（-1）

  除 PLAIN\_MESSAGE 外，每种类型对应于一个默认的图标

* Object initialValue：初始选项或输入值

### 15.9.3 JFileChooser 文件对话框

> JFileChooser 文件对话框是专门用于对文件或目录进行浏览和选择的对话框

**构造方法：**

* `new JFileChooser()`：构造一个指向客户默认目录的文件对话框
* `new JFileChooser(File currentDirectory)`：指向指定目录的文件对话框
* `new JFileChooser(String path)`：指向指定目录的文件对话框

**常用方法：**

* `showOpenDialog(Component parent)`：弹出一个 “打开” 文件对话框

  parent 是对话框的父窗口对象。其父窗口位置决定了对话框位置

  该值可以是 null，表示用默认的 Frame 作为父窗口。这个场合，对话框位置在屏幕正中

  `showSaveDialog(Component parent)`：弹出一个 “保存” 文件对话框

* `getSelectedFile()`：获得用户选择的文件
