编程学习
🗒️java图形化界面
00 分钟
2024-2-27
2024-3-2
type
status
date
slug
summary
tags
category
icon
password

一. 课程概述

通常情况下,java语言一般是用来开发后台程序的,所谓的后台程序就是部署在服务器端的程序,默默的工作,用户是看不到任何界面的,所以很多情况下,学习java会感觉很枯燥。
notion image
事实上,我们使用java语言同样可以完成图形化界面程序的开发,而学习图形化界面编程相对来说就会有趣很多,因为所见即所得,也就是说,我们写的大部分代码的执行效果,是可以通过图形化界面实实在在能够看得到的。
notion image
java使用AWT和Swing相关的类可以完成图形化界面编程,其中AWT的全称是抽象窗口工具集(Abstract Window Toolkit),它是sun公司最早提供的GUI库,这个GUI库提供了一些基本功能,但这个GUI库的功能比较有限,所以后来sun公司又提供了Swing库。通过使用AWT和Swing提供的图形化界面组件库,java的图形化界面编程非常简单,程序只需要依次创建所需的图形组件,并以合适的方式将这些组件组织在一起,就可以开发出非常美观的用户界面。
本次讲解的java开发平台是jdk9,希望大家课后练习时也使用jdk9,因为不同版本的jdk提供的GUI库的效果略有不同。

二. AWT 编程

2.1 AWT简介

当 JDK 1.0发布时, Sun 提供了 一套基本的GUI类库,这个GUI类库希望可以在所有平台下都能运行 , 这套基本类库被称为"抽象窗口工具集 CAbstract Window Toolkit )",它为Java应用程序提供了基本的图形组件 。 AWT是窗口框架,它从不同平台的窗口系统中抽取出共同组件 , 当程序运行时,将这些组件的创建和动作委托给程序所在的运行平台 。 简而言之 ,当使用 AWT 编写图形界面应用 时, 程序仅指定了界面组件的位置和行为,并未提供真正的实现,JVM调用操作系统本地的图形界面来创建和平台 一致的对等体 。
使用AWT创建的图形界面应用和所在的运行平台有相同的界面风格 , 比如在 Windows 操作系统上,它就表现出 Windows 风格 ; 在 UNIX 操作系统上,它就表现出UNIX 风格 。 Sun 希望采用这种方式来实现 " Write Once, Run Anywhere " 的目标 。

2.2 AWT继承体系

所有和 AWT 编程相关的类都放在 java.awt 包以及它的子包中, AWT 编程中有两个基类 :Component和 MenuComponent。
  • Component:代表一个能以图形化方式显示出来,并可与用户交互的对象,例如 Button 代表一个按钮,TextField 代表 一个文本框等;
  • MenuComponent:则代表图形界面的菜单组件,包括 MenuBar (菜单条)、 Menultem (菜单项)等子类。
notion image
其中 Container 是一种特殊的 Component,它代表一种容器,可以盛装普通的 Component。
AWT中还有一个非常重要的接口叫LayoutManager ,如果一个容器中有多个组件,那么容器就需要使用LayoutManager来管理这些组件的布局方式。
notion image

2.3 Container容器

2.3.1 Container继承体系

notion image
  • Winow是可以独立存在的顶级窗口,默认使用BorderLayout管理其内部组件布局;
  • Panel可以容纳其他组件,但不能独立存在,它必须内嵌其他容器中使用,默认使用FlowLayout管理其内部组件布局;
  • ScrollPane 是 一个带滚动条的容器,它也不能独立存在,默认使用 BorderLayout 管理其内部组件布局;

2.3.2 常见API

Component作为基类,提供了如下常用的方法来设置组件的大小、位置、可见性等。
方法签名
方法功能
setLocation(int x, int y)
设置组件的位置。
setSize(int width, int height)
设置组件的大小。
setBounds(int x, int y, int width, int height)
同时设置组件的位置、大小。
setVisible(Boolean b):
设置该组件的可见性。
Container作为容器根类,提供了如下方法来访问容器中的组件
方法签名
方法功能
Component add(Component comp)
向容器中添加其他组件 (该组件既可以是普通组件,也可以 是容器) , 并返回被添加的组件 。
Component getComponentAt(int x, int y):
返回指定点的组件 。
int getComponentCount():
返回该容器内组件的数量 。
Component[] getComponents():
返回该容器内的所有组件 。

2.3.3 容器演示

2.3.3.1 Window

notion image

2.3.3.2 Panel

notion image
)
由于IDEA默认使用utf-8进行编码,但是当前我们执行代码是是在windows系统上,而windows操作系统的默认编码是gbk,所以会乱码,如果出现了乱码,那么只需要在运行当前代码前,设置一个jvm参数 -Dfile.encoding=gbk即可。

2.3.3.3 ScrollPane

notion image
程序明明向 ScrollPane 容器中添加了 一个文本框和一个按钮,但只能看到 一个按钮,却看不到文本框 ,这是为什么 呢?这是因为ScrollPane 使用 BorderLayout 布局管理器的缘故,而 BorderLayout 导致了该容器中只有一个组件被显示出来 。 下一节将向详细介绍布局管理器的知识 。

2.4 LayoutManager布局管理器

之前,我们介绍了Component中有一个方法 setBounds() 可以设置当前容器的位置和大小,但是我们需要明确一件事,如果我们手动的为组件设置位置和大小的话,就会造成程序的不通用性,例如:
创建了一个lable组件,很多情况下,我们需要让lable组件的宽高和“你好,世界”这个字符串自身的宽高一致,这种大小称为最佳大小。由于操作系统存在差异,例如在windows上,我们要达到这样的效果,需要把该Lable组件的宽和高分别设置为100px,20px,但是在Linux操作系统上,可能需要把Lable组件的宽和高分别设置为120px,24px,才能达到同样的效果。
如果要让我么的程序在不同的操作系统下,都有相同的使用体验,那么手动设置组件的位置和大小,无疑是一种灾难,因为有太多的组件,需要分别设置不同操作系统下的大小和位置。为了解决这个问题,Java提供了LayoutManager布局管理器,可以根据运行平台来自动调整组件大小,程序员不用再手动设置组件的大小和位置了,只需要为容器选择合适的布局管理器即可。
notion image

2.4.1 FlowLayout

在 FlowLayout 布局管理器 中,组件像水流一样向某方向流动 (排列) ,遇到障碍(边界)就折回,重头开始排列 。在默认情况下, FlowLayout 布局管理器从左向右排列所有组件,遇到边界就会折回下一行重新开始。
构造方法
方法功能
FlowLayout()
使用默认 的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。
FlowLayout(int align)
使用指定的对齐方式及默认的垂直间距、水平间距创建 FlowLayout 布局管理器。
FlowLayout(int align,int hgap,int vgap)
使用指定的对齐方式及指定的垂直问距、水平间距创建FlowLayout 布局管理器。
FlowLayout 中组件的排列方向(从左向右、从右向左、从中间向两边等) , 该参数应该使用FlowLayout类的静态常量 : FlowLayout. LEFT 、 FlowLayout. CENTER 、 FlowLayout. RIGHT ,默认是左对齐。
FlowLayout 中组件中间距通过整数设置,单位是像素,默认是5个像素。
代码演示:
notion image

2.4.2 BorderLayout

BorderLayout 将容器分为 EAST 、 SOUTH 、 WEST 、 NORTH 、 CENTER五个区域,普通组件可以被放置在这 5 个区域的任意一个中 。 BorderLayout布局 管理器的布局示意图如图所示 。
notion image
当改变使用 BorderLayout 的容器大小时, NORTH 、 SOUTH 和 CENTER区域水平调整,而 EAST 、 WEST 和 CENTER 区域垂直调整。使用BorderLayout 有如下两个注意点:
  1. 当向使用 BorderLayout 布局管理器的容器中添加组件时 , 需要指定要添加到哪个区域中 。 如果没有指定添加到哪个区域中,则默认添加到中间区域中;
  1. 如果向同一个区域中添加多个组件时 , 后放入的组件会覆盖先放入的组件;
构造方法
方法功能
BorderLayout()
使用默认的水平间距、垂直 间距创建 BorderLayout 布局管理器 。
BorderLayout(int hgap,int vgap):
使用指定的水平间距、垂直间距创建 BorderLayout 布局管理器。
代码演示1:
notion image
如果不往某个区域中放入组件,那么该区域不会空白出来,而是会被其他区域占用
代码演示2:
notion image

2.4.3 GridLayout

GridLayout 布局管理器将容器分割成纵横线分隔的网格 , 每个网格所占的区域大小相同。当向使用 GridLayout 布局管理器的容器中添加组件时, 默认从左向右、 从上向下依次添加到每个网格中 。 与 FlowLayout不同的是,放置在 GridLayout 布局管理器中的各组件的大小由组件所处的区域决定(每 个组件将自动占满整个区域) 。
构造方法
方法功能
GridLayout(int rows,in t cols)
采用指定的行数、列数,以及默认的横向间距、纵向间距将容器 分割成多个网格
GridLayout(int rows,int cols,int hgap,int vgap)
采用指定 的行数、列 数 ,以及指定的横向间距 、 纵向间距将容器分割成多个网格。
案例:
使用Frame+Panel,配合FlowLayout和GridLayout完成一个计算器效果。
notion image
代码:

2.4.4 GridBagLayout

GridBagLayout 布局管理器的功能最强大 , 但也最复杂,与 GridLayout 布局管理器不同的是, 在GridBagLayout 布局管理器中,一个组件可以跨越一个或多个网格 , 并可以设置各网格的大小互不相同,从而增加了布局的灵活性 。 当窗口的大小发生变化时 , GridBagLayout 布局管理器也可以准确地控制窗口各部分的拉伸 。
notion image
由于在GridBagLayout 布局中,每个组件可以占用多个网格,此时,我们往容器中添加组件的时候,就需要具体的控制每个组件占用多少个网格,java提供的GridBagConstaints类,与特定的组件绑定,可以完成具体大小和跨越性的设置。
GridBagConstraints API:
成员变量
含义
gridx
设置受该对象控制的GUI组件左上角所在网格的横向索引
gridy
设置受该对象控制的GUI组件左上角所在网格的纵向索引
gridwidth
设置受该对象控制的 GUI 组件横向跨越多少个网格,如果属性值为 GridBagContraints.REMAIND,则表明当前组件是横向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是横向倒数第二个组件。
gridheight
设置受该对象控制的 GUI 组件纵向跨越多少个网格,如果属性值为 GridBagContraints.REMAIND,则表明当前组件是纵向最后一个组件,如果属性值为GridBagConstraints.RELATIVE,表明当前组件是纵向倒数第二个组件。
fill
当"显示区域"大于"组件"的时候,如何调整组件 : GridBagConstraints.NONE : GUI 组件不扩大 GridBagConstraints.HORIZONTAL: GUI 组件水平扩大 以 占据空白区域 GridBagConstraints.VERTICAL: GUI 组件垂直扩大以占据空白区域 GridBagConstraints.BOTH: GUI 组件水平 、 垂直同时扩大以占据空白区域.
ipadx
设置受该对象控制的 GUI 组件横向内部填充的大小,即 在该组件最小尺寸的基础上还需要增大多少.
ipady
设置受该对象控制的 GUI 组件纵向内部填充的大小,即 在该组件最小尺寸的基础上还需要增大多少.
insets
设置受该对象控制 的 GUI 组件的 外部填充的大小 , 即该组件边界和显示区 域边界之间的 距离 .
weightx
设置受该对象控制 的 GUI 组件占据多余空间的水平比例, 假设某个容器 的水平线上包括三个 GUI 组件, 它们的水平增加比例分别是 1 、 2 、 3 , 但容器宽度增加 60 像素 时,则第一个组件宽度增加 10 像素 , 第二个组件宽度增加 20 像素,第三个组件宽度增加 30 像 素。 如 果其增 加比例为 0 , 则 表示不会增加 。
weighty
设置受该对象控制 的 GUI 组件占据多余空间的垂直比例
anchor
设置受该对象控制 的 GUI 组件在其显示区域中的定位方式: GridBagConstraints .CENTER (中 间 ) GridBagConstraints.NORTH (上中 ) GridBagConstraints.NORTHWEST (左上角) GridBagConstraints.NORTHEAST (右上角) GridBagConstraints.SOUTH (下中) GridBagConstraints.SOUTHEAST (右下角) GridBagConstraints.SOUTHWEST (左下角) GridBagConstraints.EAST (右中) GridBagConstraints.WEST (左中)
GridBagLayout使用步骤:
案例:
使用Frame容器,设置GridBagLayout布局管理器,实现下图中的效果:
notion image
演示代码:

2.4.5 CardLayout

CardLayout 布局管理器以时间而非空间来管理它里面的组件,它将加入容器的所有组件看成一叠卡片(每个卡片其实就是一个组件),每次只有最上面的那个 Component 才可见。就好像一副扑克牌,它们叠在一起,每次只有最上面的一张扑克牌才可见.
方法名称
方法功能
CardLayout()
创建默认的 CardLayout 布局管理器。
CardLayout(int hgap,int vgap)
通过指定卡片与容器左右边界的间距 C hgap) 、上下边界 Cvgap) 的间距来创建 CardLayout 布局管理器.
first(Container target)
显示target 容器中的第一张卡片.
last(Container target)
显示target 容器中的最后一张卡片.
previous(Container target)
显示target 容器中的前一张卡片.
next(Container target)
显示target 容器中的后一张卡片.
show(Container taget,String name)
显 示 target 容器中指定名字的卡片.
案例:
使用Frame和Panel以及CardLayout完成下图中的效果,点击底部的按钮,切换卡片
notion image
演示代码:

2.4.6 BoxLayout

为了简化开发,Swing 引入了 一个新的布局管理器 : BoxLayout 。 BoxLayout 可以在垂直和 水平两个方向上摆放 GUI 组件, BoxLayout 提供了如下一个简单的构造器:
方法名称
方法功能
BoxLayout(Container target, int axis)
指定创建基于 target 容器的 BoxLayout 布局管理器,该布局管理器里的组件按 axis 方向排列。其中 axis 有 BoxLayout.X_AXIS( 横向)和 BoxLayout.Y _AXIS (纵向〉两个方向。
案例1:
使用Frame和BoxLayout完成下图效果:
notion image
演示代码1:
在java.swing包中,提供了一个新的容器Box,该容器的默认布局管理器就是BoxLayout,大多数情况下,使用Box容器去容纳多个GUI组件,然后再把Box容器作为一个组件,添加到其他的容器中,从而形成整体窗口布局。
方法名称
方法功能
static Box createHorizontalBox()
创建一个水平排列组件的 Box 容器 。
static Box createVerticalBox()
创建一个垂直排列组件的 Box 容器 。
案例2:
使用Frame和Box,完成下图效果:
notion image
演示代码2:
通过之前的两个BoxLayout演示,我们会发现,被它管理的容器中的组件之间是没有间隔的,不是特别的美观,但之前学习的几种布局,组件之间都会有一些间距,那使用BoxLayout如何给组件设置间距呢?
其实很简单,我们只需要在原有的组件需要间隔的地方,添加间隔即可,而每个间隔可以是一个组件,只不过该组件没有内容,仅仅起到一种分隔的作用。
notion image
Box类中,提供了5个方便的静态方法来生成这些间隔组件:
方法名称
方法功能
static Component createHorizontalGlue()
创建一条水平 Glue (可在两个方向上同时拉伸的间距)
static Component createVerticalGlue()
创建一条垂直 Glue (可在两个方向上同时拉伸的间距)
static Component createHorizontalStrut(int width)
创建一条指定宽度(宽度固定了,不能拉伸)的水平Strut (可在垂直方向上拉伸的间距)
static Component createVerticalStrut(int height)
创建一条指定高度(高度固定了,不能拉伸)的垂直Strut (可在水平方向上拉伸的间距)
案例3:
使用Frame和Box,完成下图效果:
notion image
演示代码3:

2.5 AWT中常用组件

2.5.1 基本组件

组件名
功能
Button
Button
Canvas
用于绘图的画布
Checkbox
复选框组件(也可当做单选框组件使用)
CheckboxGroup
用于将多个Checkbox 组件组合成一组, 一组 Checkbox 组件将只有一个可以 被选中 , 即全部变成单选框组件
Choice
下拉选择框
Frame
窗口 , 在 GUI 程序里通过该类创建窗口
Label
标签类,用于放置提示性文本
List
JU表框组件,可以添加多项条目
Panel
不能单独存在基本容器类,必须放到其他容器中
Scrollbar
滑动条组件。如果需要用户输入位于某个范围的值 , 就可以使用滑动条组件 ,比如调 色板中设置 RGB 的三个值所用的滑动条。当创建一个滑动条时,必须指定它的方向、初始值、 滑块的大小、最小值和最大值。
ScrollPane
带水平及垂直滚动条的容器组件
TextArea
多行文本域
TextField
单行文本框
这些 AWT 组件的用法比较简单,可以查阅 API 文档来获取它们各自的构方法、成员方法等详细信息。
案例:
实现下图效果:
notion image
演示代码:

2.5.2 对话框Dialog

2.5.2.1 Dialog

Dialog 是 Window 类的子类,是 一个容器类,属于特殊组件 。 对话框是可以独立存在的顶级窗口, 因此用法与普通窗口的用法几乎完全一样,但是使用对话框需要注意下面两点:
  • 对话框通常依赖于其他窗口,就是通常需要有一个父窗口;
  • 对话框有非模式(non-modal)和模式(modal)两种,当某个模式对话框被打开后,该模式对话框总是位于它的父窗口之上,在模式对话框被关闭之前,父窗口无法获得焦点。
方法名称
方法功能
Dialog(Frame owner, String title, boolean modal)
创建一个对话框对象: owner:当前对话框的父窗口 title:当前对话框的标题 modal:当前对话框是否是模式对话框,true/false
案例1:
通过Frame、Button、Dialog实现下图效果:
notion image
演示代码1:
在Dialog对话框中,可以根据需求,自定义内容
案例:
点击按钮,弹出一个模式对话框,其内容如下:
notion image
演示代码:

2.5.2.1 FileDialog

Dialog 类还有 一个子类 : FileDialog ,它代表一个文件对话框,用于打开或者保存 文件,需要注意的是FileDialog无法指定模态或者非模态,这是因为 FileDialog 依赖于运行平台的实现,如果运行平台的文件对话框是模态的,那么 FileDialog 也是模态的;否则就是非模态的 。
方法名称
方法功能
FileDialog(Frame parent, String title, int mode)
创建一个文件对话框: parent:指定父窗口 title:对话框标题 mode:文件对话框类型,如果指定为FileDialog.load,用于打开文件,如果指定为FileDialog.SAVE,用于保存文件
String getDirectory()
获取被打开或保存文件的绝对路径
String getFile()
获取被打开或保存文件的文件名
案例2:
使用 Frame、Button和FileDialog完成下图效果:
notion image
演示代码2:

2.6 事件处理

前面介绍了如何放置各种组件,从而得到了丰富多彩的图形界面,但这些界面还不能响应用户的任何操作。比如单击前面所有窗口右上角的“X”按钮,但窗口依然不会关闭。因为在 AWT 编程中 ,所有用户的操作,都必须都需要经过一套事件处理机制来完成,而 Frame 和组件本身并没有事件处理能力 。

2.6.1 GUI事件处理机制

定义:
当在某个组件上发生某些操作的时候,会自动的触发一段代码的执行。
在GUI事件处理机制中涉及到4个重要的概念需要理解:
事件源(Event Source):操作发生的场所,通常指某个组件,例如按钮、窗口等; 事件(Event):在事件源上发生的操作可以叫做事件,GUI会把事件都封装到一个Event对象中,如果需要知道该事件的详细信息,就可以通过Event对象来获取。 事件监听器(Event Listener):当在某个事件源上发生了某个事件,事件监听器就可以对这个事件进行处理。
注册监听:把某个事件监听器(A)通过某个事件(B)绑定到某个事件源(C)上,当在事件源C上发生了事件B之后,那么事件监听器A的代码就会自动执行。
notion image
使用步骤:
1.创建事件源组件对象;
2.自定义类,实现XxxListener接口,重写方法;
3.创建事件监听器对象(自定义类对象)
4.调用事件源组件对象的addXxxListener方法完成注册监听
案例:
完成下图效果,点击确定按钮,在单行文本域内显示 hello world:
notion image
演示代码:

2.6.2 GUI中常见事件和事件监听器

事件监听器必须实现事件监听器接口, AWT 提供了大量的事件监听器接口用于实现不同类型的事件监听器,用于监听不同类型的事件 。 AWT 中提供了丰富的事件类,用于封装不同组件上所发生的特定操作, AWT 的事件类都是 AWTEvent 类的子类 , AWTEvent是 EventObject 的子类。

2.6.2.1 事件

AWT把事件分为了两大类:
1.低级事件:这类事件是基于某个特定动作的事件。比如进入、点击、拖放等动作的鼠标事件,再比如得到焦点和失去焦点等焦点事件。
事件
触发时机
ComponentEvent
组件事件 , 当 组件尺寸发生变化、位置发生移动、显示/隐藏状态发生改变时触发该事件。
ContainerEvent
容器事件 , 当容器里发生添加组件、删除组件时触发该事件 。
WindowEvent
窗口事件, 当窗 口状态发生改变 ( 如打开、关闭、最大化、最 小化)时触发该事件 。
FocusEvent
焦点事件 , 当组件得到焦点或失去焦点 时触发该事件 。
KeyEvent
键盘事件 , 当按键被按下、松开、单击时触发该事件。
MouseEvent
鼠标事件,当进行单击、按下、松开、移动鼠标等动作 时触发该事件。
PaintEvent
组件绘制事件 , 该事件是一个特殊的事件类型 , 当 GUI 组件调 用 update/paint 方法 来呈现自身时触发该事件,该事件并非专用于事件处理模型 。
2.高级事件:这类事件并不会基于某个特定动作,而是根据功能含义定义的事件。
事件
触发时机
ActionEvent
动作事件 ,当按钮、菜单项被单击,在 TextField 中按 Enter 键时触发
AjustmentEvent
调节事件,在滑动条上移动滑块以调节数值时触发该事件。
ltemEvent
选项事件,当用户选中某项, 或取消选中某项时触发该事件 。
TextEvent
文本事件, 当文本框、文本域里的文本发生改变时触发该事件。

2.6.2 事件监听器

不同的事件需要使用不同的监听器监听,不同的监听器需要实现不同的监听器接口, 当指定事件发生后 , 事件监听器就会调用所包含的事件处理器(实例方法)来处理事件 。
事件类别
描述信息
监听器接口名
ActionEvent
激活组件
ActionListener
ItemEvent
选择了某些项目
ItemListener
MouseEvent
鼠标移动
MouseMotionListener
MouseEvent
鼠标点击等
MouseListener
KeyEvent
键盘输入
KeyListener
FocusEvent
组件收到或失去焦点
FocusListener
AdjustmentEvent
移动了滚动条等组件
AdjustmentListener
ComponentEvent
对象移动缩放显示隐藏等
ComponentListener
WindowEvent
窗口收到窗口级事件
WindowListener
ContainerEvent
容器中增加删除了组件
ContainerListener
TextEvent
文本字段或文本区发生改变
TextListener

2.6.3 案例

案例一:
通过ContainerListener监听Frame容器添加组件;
通过TextListener监听TextFiled内容变化;
通过ItemListener监听Choice条目选中状态变化;
notion image
演示代码一:
案例2:
给Frame设置WindowListner,监听用户点击 X 的动作,如果用户点击X,则关闭当前窗口
演示代码2:

2.7 菜单组件

前面讲解了如果构建GUI界面,其实就是把一些GUI的组件,按照一定的布局放入到容器中展示就可以了。在实际开发中,除了主界面,还有一类比较重要的内容就是菜单相关组件,可以通过菜单相关组件很方便的使用特定的功能,在AWT中,菜单相关组件的使用和之前学习的组件是一模一样的,只需要把菜单条、菜单、菜单项组合到一起,按照一定的布局,放入到容器中即可。
notion image
下表中给出常见的菜单相关组件:
菜单组件名称
功能
MenuBar
菜单条 , 菜单的容器 。
Menu
菜单组件 , 菜单项的容器 。 它也是Menultem的子类 ,所以可作为菜单项使用
PopupMenu
上下文菜单组件(右键菜单组件)
Menultem
菜单项组件 。
CheckboxMenuItem
复选框菜单项组件
下图是常见菜单相关组件集成体系图:
notion image
菜单相关组件使用:
1.准备菜单项组件,这些组件可以是MenuItem及其子类对象
2.准备菜单组件Menu或者PopupMenu(右击弹出子菜单),把第一步中准备好的菜单项组件添加进来;
3.准备菜单条组件MenuBar,把第二步中准备好的菜单组件Menu添加进来;
4.把第三步中准备好的菜单条组件添加到窗口对象中显示。
小技巧:
1.如果要在某个菜单的菜单项之间添加分割线,那么只需要调用Menu的add(new MenuItem(-))即可。
2.如果要给某个菜单项关联快捷键功能,那么只需要在创建菜单项对象时设置即可,例如给菜单项关联 ctrl+shif+/ 快捷键,只需要:new MenuItem("菜单项名字",new MenuShortcut(KeyEvent.VK_Q,true);
案例1:
使用awt中常用菜单组件,完成下图效果
notion image
演示代码1:
案例2:
通过PopupMenu实现下图效果:
notion image
实现思路:
1.创建PopubMenu菜单组件;
2.创建多个MenuItem菜单项,并添加到PopupMenu中;
3.将PopupMenu添加到目标组件中;
4.为需要右击出现PopubMenu菜单的组件,注册鼠标监听事件,当监听到用户释放右键时,弹出菜单。
演示代码2:

2.8 绘图

很多程序如各种小游戏都需要在窗口中绘制各种图形,除此之外,即使在开发JavaEE项目时, 有 时候也必须"动态"地向客户 端生成各种图形、图表,比如 图形验证码、统计图等,这都需要利用AWT的绘图功能。

2.8.1 组件绘图原理

之前我们已经学习过很多组件,例如Button、Frame、Checkbox等等,不同的组件,展示出来的图形都不一样,其实这些组件展示出来的图形,其本质就是用AWT的绘图来完成的。
在AWT中,真正提供绘图功能的是Graphics对象,那么Component组件和Graphics对象存在什么关系,才能让Component绘制自身图形呢?在Component类中,提供了下列三个方法来完成组件图形的绘制与刷新:
paint(Graphics g):绘制组件的外观;
update(Graphics g):内部调用paint方法,刷新组件外观;
repaint():调用update方法,刷新组件外观;
notion image
一般情况下,update和paint方法是由AWT系统负责调用,如果程序要希望系统重新绘制组件,可以调用repaint方法完成。

2.8.2 Graphics类的使用

实际生活中如果需要画图,首先我们得准备一张纸,然后在拿一支画笔,配和一些颜色,就可以在纸上画出来各种各样的图形,例如圆圈、矩形等等。
notion image
程序中绘图也一样,也需要画布,画笔,颜料等等。AWT中提供了Canvas类充当画布,提供了Graphics类来充当画笔,通过调用Graphics对象的setColor()方法可以给画笔设置颜色。
画图的步骤:
1.自定义类,继承Canvas类,重写paint(Graphics g)方法完成画图;
2.在paint方法内部,真正开始画图之前调用Graphics对象的setColor()、setFont()等方法设置画笔的颜色、字体等属性;
3.调用Graphics画笔的drawXxx()方法开始画图。
其实画图的核心就在于使用Graphics画笔在Canvas画布上画出什么颜色、什么样式的图形,所以核心在画笔上,下表中列出了Graphics类中常用的一些方法:
方法名称
方法功能
setColor(Color c)
设置颜色
setFont(Font font)
设置字体
drawLine()
绘制直线
drawRect()
绘制矩形
drawRoundRect()
绘制圆角矩形
drawOval()
绘制椭圆形
drawPolygon()
绘制多边形
drawArc()
绘制圆弧
drawPolyline()
绘制折线
fillRect()
填充矩形区域
fillRoundRect()
填充圆角矩形区域
fillOval()
填充椭圆区域
fillPolygon()
填充多边形区域
fillArc()
填充圆弧对应的扇形区域
drawImage()
绘制位图
案例:
使用AWT绘图API,完成下图效果
notion image
演示代码:
Java也可用于开发一些动画。所谓动画,就是间隔一定的时间(通常小于0 . 1秒 )重新绘制新的图像,两次绘制的图像之间差异较小,肉眼看起来就成了所谓的动画 。
为了实现间隔一定的时间就重新调用组件的 repaint()方法,可以借助于 Swing 提供的Timer类,Timer类是一个定时器, 它有如下一个构造器 : Timer(int delay, ActionListener listener): 每间隔 delay 毫秒,系统自动触发 ActionListener 监听器里的事件处理器方法,在方法内部我们就可以调用组件的repaint方法,完成组件重绘。
案例2:
使用AWT画图技术及Timer定时器,完成下图中弹球小游戏。
notion image
notion image
演示代码2:

2.8.3 处理位图

如果仅仅绘制一些简单的几何图形,程序的图形效果依然比较单调 。 AWT 也允许在组件上绘制位图, Graphics 提供了 drawlmage() 方法用于绘制位图,该方法需要一个Image参数一一代表位图,通过该方法就可 以绘制出指定的位图 。
位图使用步骤:
1.创建Image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高及类型属性;此时相当于在内存中生成了一张图片;
2.调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的这张图片上绘图了,绘图的方法和之前学习的一模一样;
3.调用组件的drawImage()方法,一次性的内存中的图片BufferedImage绘制到特定的组件上。
使用位图绘制组件的好处:
使用位图来绘制组件,相当于实现了图的缓冲区,此时绘图时没有直接把图形绘制到组件上,而是先绘制到内存中的BufferedImage上,等全部绘制完毕,再一次性的图像显示到组件上即可,这样用户的体验会好一些。
案例:
通过BufferedImage实现一个简单的手绘程序:通过鼠标可以在窗口中画图。
notion image
演示代码:

2.8.4 ImageIO的使用

在实际生活中,很多软件都支持打开本地磁盘已经存在的图片,然后进行编辑,编辑完毕后,再重新保存到本地磁盘。如果使用AWT要完成这样的功能,那么需要使用到ImageIO这个类,可以操作本地磁盘的图片文件。
方法名称
方法功能
static BufferedImage read(File input)
读取本地磁盘图片文件
static BufferedImage read(InputStream input)
读取本地磁盘图片文件
static boolean write(RenderedImage im, String formatName, File output)
往本地磁盘中输出图片文件
案例:
编写图片查看程序,支持另存操作
notion image
演示代码:

2.8.5 五子棋

接下来,我们使用之前学习的绘图技术,做一个五子棋的游戏。
notion image
演示代码:

三. Swing 编程

3.1 Swing概述

前一章己经介绍过AWT和Swing 的关系 , 因此不难知道 : 实际使用 Java 开发图形界面程序时 ,很少使用 AWT 组件,绝大部分时候都是用 Swing 组件开发的 。 Swing是由100%纯 Java实现的,不再依赖于本地平台的 GUI, 因此可以在所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为轻量级组件;而依赖于本地平台的 AWT 组件被称为重量级组件。 由于 Swing 的所有组件完全采用 Java 实现,不再调用本地平台的 GUI,所以导致 Swing 图形界面的显示速度要比 AWT 图形界面的显示速度慢一些,但相对于快速发展的硬件设施而言,这种微小的速度差别无妨大碍。
使用Swing的优势: 1. Swing 组件不再依赖于本地平台的 GUI,无须采用各种平台的 GUI 交集 ,因此 Swing 提供了大量图形界面组件 , 远远超出了 AWT 所提供的图形界面组件集。 2. Swing 组件不再依赖于本地平台 GUI ,因此不会产生与平台 相关的 bug 。
3. Swing 组件在各种平台上运行时可以保证具有相同的图形界面外观。
Swing 提供的这些优势,让 Java 图形界面程序真正实现了 " Write Once, Run Anywhere" 的 目标。
Swing的特征: 1. Swing 组件采用 MVC(Model-View-Controller, 即模型一视图一控制器)设计模式:

3.2 Swing基本组件的用法

3.2.1 Swing组件层次

Swing组件继承体系图:
notion image
大部分Swing 组件都是 JComponent抽象类的直接或间接子类(并不是全部的 Swing 组件),JComponent 类定义了所有子类组件的通用方法 ,JComponent 类是 AWT 里 java.awt. Container 类的子类 ,这也是 AWT 和 Swing 的联系之一。 绝大部分 Swing 组件类继承了 Container类,所以Swing 组件都可作为 容器使用 ( JFrame继承了Frame 类)。
Swing组件和AWT组件的对应关系:
大部分情况下,只需要在AWT组件的名称前面加个J,就可以得到其对应的Swing组件名称,但有几个例外:
1. JComboBox: 对应于 AWT 里的 Choice 组件,但比 Choice 组件功能更丰富 。 2. JFileChooser: 对应于 AWT 里的 FileDialog 组件 。 3. JScrollBar: 对应于 AWT 里的 Scrollbar 组件,注意两个组件类名中 b 字母的大小写差别。 4. JCheckBox : 对应于 AWT 里的 Checkbox 组件, 注意两个组件类名中 b 字母的大小 写差别 。 5. JCheckBoxMenultem: 对应于 AWT 里的 CheckboxMenuItem 组件,注意两个组件类名中 b字母的大小写差别。
Swing组件按照功能来分类:
1. 顶层容器: JFrame、JApplet、JDialog 和 JWindow 。 2. 中间容器: JPanel 、 JScrollPane 、 JSplitPane 、 JToolBar 等 。 3. 特殊容器:在用户界面上具有特殊作用的中间容器,如 JIntemalFrame 、 JRootPane 、 JLayeredPane和 JDestopPane 等 。 4. 基本组件 : 实现人机交互的组件,如 JButton、 JComboBox 、 JList、 JMenu、 JSlider 等 。 5. 不可编辑信息的显示组件:向用户显示不可编辑信息的组件,如JLabel 、 JProgressBar 和 JToolTip等。 6. 可编辑信息的显示组件:向用户显示能被编辑的格式化信息的组件,如 JTable 、 JTextArea 和JTextField 等 。 7. 特殊对话框组件:可以直接产生特殊对话框的组件 , 如 JColorChooser 和 JFileChooser 等。

3.2.2 AWT组件的Swing实现

Swing 为除 Canvas 之外的所有 AWT 组件提供了相应的实现,Swing 组件比 AWT 组件的功能更加强大。相对于 AWT 组件, Swing 组件具有如下 4 个额外的功能 :
  1. 可以为 Swing 组件设置提示信息。使用 setToolTipText()方法,为组件设置对用户有帮助的提示信息 。
  1. 很多 Swing 组件如按钮、标签、菜单项等,除使用文字外,还可以使用图标修饰自己。为了允许在 Swing 组件中使用图标, Swing为Icon 接口提供了 一个实现类: Imagelcon ,该实现类代表一个图像图标。
  1. 支持插拔式的外观风格。每个 JComponent 对象都有一个相应的 ComponentUI 对象,为它完成所有的绘画、事件处理、决定尺寸大小等工作。 ComponentUI 对象依赖当前使用的 PLAF , 使用 UIManager.setLookAndFeel()方法可以改变图形界面的外观风格 。
  1. 支持设置边框。Swing 组件可以设置一个或多个边框。 Swing 中提供了各式各样的边框供用户边 用,也能建立组合边框或自己设计边框。 一种空白边框可以用于增大组件,同时协助布局管理器对容器中的组件进行合理的布局。
每个 Swing 组件都有一个对应的UI 类,例如 JButton组件就有一个对应的 ButtonUI 类来作为UI代理 。每个 Swing组件的UI代理的类名总是将该 Swing 组件类名的 J 去掉,然后在后面添加 UI 后缀 。 UI代理类通常是一个抽象基类 , 不同的 PLAF 会有不同的UI代理实现类 。 Swing 类库中包含了几套UI代理,分别放在不同的包下, 每套UI代理都几乎包含了所有 Swing组件的 ComponentUI实现,每套这样的实现都被称为一种PLAF 实现 。以 JButton 为例,其 UI 代理的继承层次下图:
notion image
如果需要改变程序的外观风格, 则可以使用如下代码:
案例:
使用Swing组件,实现下图中的界面效果:
notion image
notion image
演示代码:
注意细节:
1.Swing菜单项指定快捷键时必须通过组件名.setAccelerator(keyStroke.getKeyStroke("大写字母",InputEvent.CTRL_MASK))方法来设置,其中KeyStroke代表一次击键动作,可以直接通过按键对应字母来指定该击键动作 。
2.更新JFrame的风格时,调用了SwingUtilities.updateComponentTreeUI(f.getContentPane());这是因为如果直接更新 JFrame 本身 ,将会导致 JFrame 也被更新, JFrame 是一个特殊的容器 , JFrame 依然部分依赖于本地平台的图形组件 。如果强制 JFrame 更新,则有可能导致该窗口失去标题栏和边框 。
3.给组件设置右键菜单,不需要使用监听器,只需要调用setComponentPopupMenu()方法即可,更简单。
4.关闭JFrame窗口,也无需监听器,只需要调用setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)方法即可,更简单。
5.如果需要让某个组件支持滚动条,只需要把该组件放入到JScrollPane中,然后使用JScrollPane即可。

3.2.3 为组件设置边框

很多情况下,我们常常喜欢给不同的组件设置边框,从而让界面的层次感更明显,swing中提供了Border对象来代表一个边框,下图是Border的继承体系图:
notion image
特殊的Border:
给组件设置边框步骤:
案例:
请使用Border实现下图效果:
notion image
演示代码:

3.2.4 使用JToolBar创建工具条

Swing 提供了JToolBar类来创建工具条,并且可以往JToolBar中添加多个工具按钮。
JToolBar API:
方法名称
方法功能
JToolBar( String name , int orientation)
创建一个名字为name,方向为orientation的工具条对象,其orientation的是取值可以是SwingConstants.HORIZONTAL或SwingConstants.VERTICAL
JButton add(Action a)
通过Action对象为JToolBar工具条添加对应的工具按钮
addSeparator( Dimension size )
向工具条中添加指定大小的分隔符
setFloatable( boolean b )
设定工具条是否可以被拖动
setMargin(Insets m)
设置工具条与工具按钮的边距
setOrientation( int o )
设置工具条的方向
setRollover(boolean rollover)
设置此工具条的rollover状态
add(Action a)方法:
上述API中add(Action a)这个方法比较难理解,为什么呢,之前说过,Action接口是ActionListener的一个子接口,那么它就代表一个事件监听器,而这里add方法是在给工具条添加一个工具按钮,为什么传递的是一个事件监听器呢?
首先要明确的是不管是菜单条中的菜单项还是工具条中的工具按钮,最终肯定是需要点击来完成一些操作,所以JToolBar以及JMenu都提供了更加便捷的添加子组件的方法add(Action a),在这个方法的内部会做如下几件事:
案例:
使用JToolBar组件完成下图效果:
notion image
演示代码:

3.2.5 JColorChooser 和JFileChooser

Swing提供了JColorChooser和JFileChooser这两种对话框,可以很方便的完成颜色的选择和本地文件的选择。

3.2.5.1 JColorChooser

JColorChooser 用于创建颜色选择器对话框 , 该类的用法非常简单,只需要调用它的静态方法就可以快速生成一个颜色选择对话框:
案例:
使用颜色选择器,完成下图功能:
点击按钮,改变文本域的背景色
notion image
演示代码:

3.2.5.2 JFileChooser

JFileChooser 的功能与AWT中的 FileDialog 基本相似,也是用于生成"打开文件"、"保存文件 "对话框。与 FileDialog 不同的是 , JFileChooser 无须依赖于本地平台的 GUI , 它由 100%纯 Java 实现 , 在所有平台 上具有完全相同的行为,并可以在所有平台上具有相同的外观风格。
JFileChooser使用步骤:
  1. 创建JFileChooser对象:
  1. 调用JFileChooser的一系列可选方法,进行初始化
  1. 打开文件对话框
  1. 获取用户选择的结果
案例:
使用JFileChooser完成下图效果:
notion image
演示代码:

3.2.7 使用JOptionPane

3.2.7.1 基本概述

通过 JOptionPane 可以非常方便地创建一些简单的对话框, Swing 已经为这些对话框添加了相应的组件,无须程序员手动添加组件 。 JOptionPane 提供了如下 4 个方法来创建对话框 。
方法名称
方法功能
showMessageDialog/showInternalMessageDialog
消息对话框 ,告知用户某事己发生 , 用户只能单击"确定"按钮 , 类似于 JavaScript 的 alert 函数 。
showConfirmDialog/showInternalConfirmDialog
确认对话框,向用户确认某个问题,用户可以选择 yes 、 no ~ cancel 等选项 。 类似于 JavaScript 的 comfirm 函数 。该方法返回用户单击了 哪个按钮
showInputDialog/showInternalInputDialog
输入对话框,提示要求输入某些信息,类似于 JavaScript的 prompt 函数。该方法返回用户输入的字符串 。
showOptionDialog/showInternalOptionDialog
自定义选项对话框 ,允许使用自 定义选项 ,可以取代showConfirmDialog 所产生的对话框,只是用起来更复杂 。
上述方法都有都有很多重载形式,选择其中一种最全的形式,参数解释如下:
当用户与对话框交互结束后,不同类型对话框的返回值如下:
  • showMessageDialog: 无返回值 。
  • showlnputDialog: 返回用户输入或选择的字符串 。
  • showConfirmDialog: 返回 一个整数代表用户选择的选项 。
  • showOptionDialog : 返回 一个整数代表用户选择的选项,如果用户选择第一项,则返回 0; 如果选择第二项,则返回1……依此类推 。
对 showConfirmDialog 所产生的对话框,有如下几个返回值:
  • YES OPTION: 用户 单击了 "是"按钮后返回 。
  • NO OPTION: 用 户单击了"否"按钮后返回 。
  • CANCEL OPTION: 用户单击了"取消"按钮后返回 。
  • OK OPTION : 用户单击了"确定"按钮后返回 。
  • CLOSED OPTION: 用户 单击了对话框右上角的 " x" 按钮后返回。

3.2.7.2 四种对话框演示

消息对话框:
notion image
确认对话框:
notion image
输入对话框:
notion image
notion image
选项对话框:
notion image

3.3 Swing中的特殊容器

Swing提供了一些具有特殊功能的容器 , 这些特殊容器可以用于创建一些更复杂的用户界面。

3.3.1 使用JSplitPane

JSplitPane 用于创建一个分割面板,它可以将 一个组件(通常是一个容器)分割成两个部分,并提供一个分割条 , 用户可以拖动该分割条来调整两个部分的大小。
notion image
JSplitPane使用步骤:
  1. 创建JSplitPane对象
  1. 设置是否开启连续布局的支持(可选)
  1. 设置是否支持"一触即展"的支持(可选)
  1. 其他设置
案例:
使用JSplitPane实现下图效果:
点击右侧的图书名称,在左上方显示该图书的图片,左下方显示该图书的描述
notion image
演示代码:

3.3.2 使用JTabledPane

JTabbedPane可以很方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器具有相同大小的组件摆放区域。通过这种方式, 就可以在一个容器里放置更多的组件 , 例如右击桌面上的" 我的电脑 "图标,在弹出的快捷菜单里单击"属性 " 菜单工页 , 就可以看 到 一个"系统属性 " 对话框 ,这个对话框里包含了 若干个标签页。
notion image
如果需要使用JTabbedPane在窗口上创建标签页 ,则可以按如下步骤进行:
  1. 创建JTabbedPane对象
  1. 通过JTabbedPane对象堆标签进行增删改查
  1. 设置当前显示的标签页
  1. 设置JTabbedPane的其他属性
  1. 为JTabbedPane设置监听器
案例:
请使用JTabbedPane完成下图功能:
notion image
演示代码:

3.3.3 使用JLayeredPane、JDesktopPane、JInternalFrame

3.3.3.1 JLayeredPane

JLayeredPane是 一个代表有层 次深度的容器 , 它允许组件在需要 时 互相重叠。当向JLayeredPane容器中添加组件时, 需要为该组件指定一个深度索引 , 其中层次索引较高 的层里的组件位于其他层的组件之上。
notion image
JLayeredPane 还将容器的层次深度分成几个默认层 ,程序只是将组件放入相应 的层 ,从而可以更容易地确保组件的正确重叠 , 无须为组件指定具体的深度索引。JLayeredPane 提供了如下几个默认层:
  1. DEFAULT_LAYER:大多数组件位于标准层,这是最底层;
  1. PALETTE_LAYER : 调色板层位于默认层之上 。该层对于浮动工具栏和调色板很有用,因此可以位于其他组件之上 。
  1. MODAL_LAYER: 该层用于显示模式对话框。它们将出现在容器中所有工具栏 、调色板或标准组件的上面 。
  1. POPUP_LAYER : 该层用于显示右键菜单 , 与对话框 、工具提示和普通组件关联的弹出式窗口将出现在对应的对话框、工具提示和普通组件之上。
  1. DRAG_LAYER: 该层用于放置拖放过程中的组件(关于拖放操作请看下一节内 容) ,拖放操作中的组件位于所有组件之上 。 一旦拖放操作结束后 , 该组件将重新分配到其所属的正常层。
JLayeredPane 方法:
  1. moveToBack(Component c):把当前组件c移动到所在层的所有组件的最后一个位置;
  1. moveToFront(Component c):把当前组件c移动到所在层的所有组件的第一个位置;
  1. setLayer(Component c, int layer):更改组件c所处的层;
需要注意的是,往JLayeredPane中添加组件,如果要显示,则必须手动设置该组件在容器中显示的位置以及大小。
案例:
使用JLayeredPane完成下图效果:
notion image
演示代码:

3.3.3.2 JDesktopPane和JInternalFrame

JDesktopPane是JLayeredPane的子类,这种容器在开发中会更常用很多应用程序都需要启动多个内部窗口来显示信息(典型的比如IDEA、NotePad++),这些内部窗口都属于同一个外部窗口,当外部窗 口 最小化时, 这些内部窗口都被隐藏起来。在 Windows 环境中,这 种用户界面被称为多文档界面 C Multiple Document Interface, MDI) 。
使用 Swing 可以非常简单地创建出这种 MDI 界面 , 通常,内部窗口有自己的标题栏、标题、图标、三个窗口按钮,并允许拖动改变内部窗口 的大小和位置,但内部窗口不能拖出外部窗口。
notion image
JDesktopPane 需要和 JIntemalFrame 结合使用,其中JDesktopPane 代表一 个虚拟桌面 ,而JIntemalFrame则用于创建内部窗口。使用 JDesktopPane 和 JIntemalFrame 创建内部窗口按如下步骤进行即可:
  1. 创建 一 个 JDesktopPane 对象,代表虚拟桌面
  1. 使用 JIntemalFrame 创建一个内部窗口
  1. 一旦获得了内部窗口之后,该窗口的用法和普通窗口的用法基本相似, 一样可以指定该窗口的布局管理器, 一样可以向窗口内添加组件、改变窗口图标等。
  1. 将该内部窗口以合适大小、在合适位置显示出来 。与普通窗口类似的是, 该窗口默认大小是 0x0像素,位于0,0 位置(虚拟桌面的左上角处),并且默认处于隐藏状态,程序可以通过如下代码将内部窗口显示出来:
  1. 将内部窗口添加到 JDesktopPane 容器中,再将 JDesktopPane 容器添加到其他容器中。
案例:
请使用JDesktopPane和JInternalFrame完成下图效果:
notion image
演示代码:

3.4 JProcessBar、ProcessMonitor、BoundedRangeModel实现进度条

进度条是图形界面中广泛使用的GUI 组件,当复制一个较大的文件时,操作系统会显示一个进度条,用于标识复制操作完成的比例 : 当启动 Eclipse 等程序时, 因为需要加载较多的资源 , 故而启动速度较慢 , 程序也会在启动过程中显示一个进度条 , 用以表示该软件启动完成的比例 ……

3.4.1 创建进度条

使用JProgressBar创建进度条的步骤:
  1. 创建JProgressBar对象
  1. 设置属性
  1. 获取和设置当前进度条的进度状态
案例:
请使用JProgressBar完成下图效果:
notion image
演示代码:
在刚才的程序中,通过for循环来不断的更新进度条的进度,这仅仅是为了演示而已,实际开发中这样的操作是没有意义的。通常情况下是不断的检测一个耗时任务的完成情况,然后才去更新进度条的进度。下面的代码通过Timer定时器和Runnable接口,对上述代码进行改进,其运行结果没有变化,知识修改到了进度条进度更新的逻辑。
之前我们学习过,其实Swing中很多组件的界面与数据都采用了MVC的设计思想:
notion image
Swing 组件大都将外观显示和 内部数据分离 , JProgressBar 也不例外, JProgressBar 组件有一个内置的用于保存其状态数据的Model对象 , 这个对象由BoundedRangeModel对象表示,程序调用JProgressBar对象的方法完成进度百分比的设置,监听进度条的数据变化,其实都是通过它内置的BoundedRangeModel对象完成的。下面的代码是对之前代码的改进,通过BoundedRangeModel完成数据的设置,获取与监听。

3.4.2 创建进度对话框

ProgressMonitor的用法与JProgressBa 的用法基本相似,只是ProgressMonitor可以直接创 建一个进度对话框,它提供了下面的构造器完成对话框的创建:
使用 ProgressMonitor 创建的对话框里包含的进度条是非常固定的,程序甚至不能设置该进度条是否包含边框(总是包含边框) , 不能设置进度不确定,不能改变进度条的方向(总是水平方向) 。
案例:
使用ProgressMonitor完成下图效果:
notion image
演示代码:

3.5 JList、JComboBox实现列表框

无论从哪个角度来看, JList 和 JComboBox 都是极其相似的,它们都有一个列表框,只是 JComboBox的列表框需要 以下拉方式显示出来; JList 和 JComboBox 都可以通过调用 setRendererO方法来改变列表项的表现形式 。甚至维护这两个组件的 Model 都是相似的, JList 使用 ListModel, JComboBox 使用ComboBoxModel ,而 ComboBoxModel 是 ListModel 的子类 。

3.5.1 简单列表框

使用JList或JComboBox实现简单列表框的步骤:
  1. 创建JList或JComboBox对象
  1. 设置JList或JComboBox的外观行为
  1. 设置监听器,监听列表项的变化,JList通过addListSelectionListener完成,JComboBox通过addItemListener完成
案例:
使用JList和JComboBox完成下图效果:
notion image
演示代码:

3.5.2 不强制存储列表项的ListModel和ComboBoxModel

与JProgressBar一样,JList和JComboBox也采用了MVC的设计模式,JList和JComboBox只负责外观的显示,而组件底层的状态数据则由对应的Model来维护。JList对应的Model是ListModel接口,JComboBox对应的Model是ComboBox接口,其代码如下:
从上面接口来看,这个 ListMode l 不管 JList 里的所有列表项的存储形式,它甚至不强制存储所有的列表项,只要 ListModel的实现类提供了getSize()和 getElementAt()两个方法 , JList 就可以根据该ListModel 对象来生成列表框 。ComboBoxModel 继承了 ListModel ,它添加了"选择项"的概念,选择项代表 JComboBox 显示区域内可见的列表项 。
在使用JList和JComboBox时,除了可以使用jdk提供的Model实现类,程序员自己也可以根据需求,自己定义Model的实现类,实现对应的方法使用。
案例:
自定义NumberListModel和NumberComboBoxModel实现类,允许使用数值范围来创建JList和JComboBox
notion image
演示代码:

3.5.3 强制存储列表项的DefaultListModel和DefaultComboBoxModel

前面只是介绍了如何创建 JList 、 JComboBox 对象, 当 调用 JList 和 JComboBox构造方法时时传入数组或 Vector 作为参数,这些数组元素或集合元素将会作为列表项。当使用JList 或 JComboBox 时 常常还需要动态地增加、删除列表项,例如JCombox提供了下列方法完成增删操作:
JList 并没有提供这些类似的方法。如果需要创建一个可以增加、删除列表项的 JList 对象,则应该在创建 JLi st 时显式使用 DefaultListModel作为构造参数 。因为 DefaultListModel 作为 JList 的 Model,它负责维护 JList 组件的所有列表数据,所以可以通过向 DefaultListModel 中添加、删除元素来实现向 JList 对象中增加 、删除列表项 。DefaultListModel 提供了如下几个方法来添加、删除元素:
案例:
使用DefaultListModel完成下图效果:
notion image
演示代码:

3.5.4 使用ListCellRenderer改变列表外观

前面程序中的 JList 和 JComboBox 采用的都是简单的字符串列表项, 实际上 , JList 和 JComboBox还可以支持图标列表项,如果在创建 JList 或 JComboBox 时传入图标数组,则创建的 JList 和 JComboBox的列表项就是图标 。
如果希望列表项是更复杂 的组件,例如,希望像 QQ 程序那样每个列表项既有图标,此时需要使用ListCellRenderer接口的实现类对象,自定义每个条目组件的渲染过程:
通过JList的setCellRenderer(ListCellRenderer<? super E> cellRenderer)方法,把自定义的ListCellRenderer对象传递给JList,就可以按照自定义的规则绘制列表项组件了。
案例:
使用ListCellRenderer实现下图效果:
notion image
演示代码:

3.6 JTree、TreeModel实现树

树也是图形用户界面中使用非常广泛的 GUI 组件,例如使用 Windows 资源管理器时,将看到如下图所示的目录树:
notion image
如上图所示的树,代表计算机世界里的树,它从自然界实际的树抽象而来 。 计算机世界里的树是由一系列具有严格父子关系的节点组成的,每个节点既可以是其上一级节点的子节点,也可以是其下一级节点的父节点,因此同一个节点既可以是父节点,也可以是子节点(类似于一个人,他既是他儿子的父亲,又是他父亲的儿子)。
按照结点是否包含子结点,可以把结点分为下面两类:
普通结点:包含子结点的结点;
叶子结点:没有子结点的结点;
按照结点是否具有唯一的父结点,可以把结点分为下面两类:
根结点:没有父结点的结点,计算机中,一棵树只能有一个根结点
普通结点:具有唯一父结点的结点
使用 Swing 里的 Jtree 、 TreeModel 及其相关的辅助类可以很轻松地开发出计算机世界里的树。

3.6.1 创建树

Swing 使用 JTree 对 象来代表一棵树,JTree 树中结点可以使用 TreePath 来标识,该对象封装了当前结点及其所有的父结点。
当一个结点具有子结点时,该结点有两种状态:
展开状态:当父结点处于展开状态时,其子结点是可见的;
折叠状态: 当父结点处于折叠状态时,其子结点都是不可见的 。
如果某个结点是可见的,则该结点的父结点(包括直接的、间接的父结点)都必须处于展开状态,只要有任意一个父结点处于折叠状态,该结点就是不可见的 。
JTree常用构造方法:
TreeNode继承体系及使用:
在构建目录树时,可以先创建很多DefaultMutableTreeNode对象,并调用他们的add方法构建好子父级结构,最后根据根结点构建一个JTree即可。
案例:
使用JTree和TreeNode完成下图效果:
演示代码:
JTree的其他外观设置方法:
DefaultMutableTreeNode其他成员方法:

3.6.2 拖动、编辑树结点

JTree 生成的树默认是不可编辑的,不可以添加、删除结点,也不可以改变结点数据 :如果想让某个 JTree 对象变成可编辑状态,则可以调用 JTree 的setEditable(boolean b)方法,传入 true 即可把这棵树变成可编辑的树(可以添加、删除结点,也可以改变结点数据) 。
编辑树结点的步骤:
  1. 获取当前被选中的结点:
  1. 调用DefaultTreeModel数据模型有关增删改的一系列方法完成编辑,方法执行完后,会自动重绘JTree
案例:
使用JTree以及DefaultTreeModel、DefaultMutableTreeNode、TreePath完成下图效果:
演示代码:

3.6.3 监听结点事件

修改JTree的选择模式:
JTree 专门提供了 一个 TreeSelectionModel 对象来保存该 JTree 选中状态的信息 。 也就是说,JTree组件背后隐藏了两个 model 对象 , 其中TreeModel 用于保存该 JTree 的所有节点数据 , 而TreeSelectionModel 用于保存该 JTree的所有选中状态的信息 。
程序可以改变 JTree 的选择模式 , 但必须先获取该 JTree 对应的 TreeSelectionMode1 对象 , 再调用该对象的 setSelectionMode(int mode);方法来设置该JTree的选择模式 ,其中model可以有如下3种取值:
  1. TreeSelectionModel.CONTIGUOUS_TREE_SELECTION: 可 以连续选中多个 TreePath 。
  1. TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : 该选项对于选择没有任何限制 。
  1. TreeSelectionModel.SINGLE_TREE_SELECTION: 每次只能选择一个 TreePath 。
为JTree添加监听器:
  1. addTreeExpansionListener(TreeExpansionListener tel) : 添加树节点展开/折叠事件的监听器。
  1. addTreeSelectionListener(TreeSelectionListener tsl) : 添加树节点选择事件的监听器。
案例:
实现下图效果:
演示代码:

3.6.4 使用DefaultTreeCellRenderer改变结点外观

JTree默认的外观是比较单一的,它提供了如下几种改变结点外观的方式:
  1. 使用 DefaultTreeCellRenderer 直接改变节点的外观,这种方式可 以 改变整棵树所有节点 的字体、颜色和图标 。
  1. 为 JTree 指定 DefaultTreeCellRenderer 的扩展类对象作为 JTree 的节点绘制器,该绘制器负责为不同节点使用不同的字体、颜色和图标。通常使用这种方式来改变节点的外观 。
  1. 为 JTree 指定一个实现 TreeCellRenderer 接口的节点绘制器,该绘制器可以为不同的节点自由绘制任意内容,这是最复杂但最灵活的节点绘制器 。
第 一种方式最简单 , 但灵活性最差 ,因为它会改变整棵树所有节点的外观 。 在这种情况下 , Jtree的所有节点依然使用相同的图标 ,相当于整体替换了 Jtree 中 节点的所有默认图标 。 用户指定 的节点图标未必就比 JTree 默认的图标美观 。
DefaultTreeCellRenderer 提供了如下几个方法来修改节点的外观:
案例:
使用DefaultTreeCellRenderer完成下图效果:
notion image
演示代码:

3.6.5 扩展DefaultTreeCellRenderer改变结点外观

DefaultTreeCellRenderer 实现类实现了TreeCellRenderer接口,该接口里只有 一个用于绘制节点内容的方法: getTreeCellRendererComponent() , 该方法负责绘制 JTree 节点 。学习JList的时候,如果要绘制JList的列表项外观的内容,需要实现ListCellRenderer 接口,通过重写getTreeCellRendererComponent()方法返回一个Component 对象 , 该对象就是 JTree 的节点组件 。两者之间非常类似
DefaultTreeCellRende rer 类继承了JLabel,实现 getTreeCellRendererComponent()方法时返回 this ,即返回一个特殊的 JLabel 对象 。 如果需要根据节点内容来改变节点的外观,则可以再次扩展DefaultTreeCellRenderer 类,并再次重写它提供的 getTreeCellRendererComponent()方法。
案例:
自定义类继承DefaultTreeCellRenderer,重写getTreeCellRendererComponent()方法,实现下图效果:
演示代码:

3.6.6 实现TreeCellRenderer接口改变结点外观

这种改变结点外观的方式是最灵活的,程序实现TreeCellRenderer接口时同样需要实现getTreeCellRendererComponent()方法,该方法可以返回任意类型的组件,该组件将作为JTree的结点。通过这种方式可以最大程度的改变结点的外观。
案例:
自定义类,继承JPanel类,实现TreeCellRenderer接口,完成下图效果:
演示代码:

3.7 JTable、TableModel实现表格

表格也是GUI程序中常用的组件,表格是一个由多行、多列组成的二维显示区 。 Swing 的 JTable 以及相关类提供了这种表格支持 , 通过使用 JTable 以及相关类,程序既可以使用简单的代码创建出表格来显示二维数据,也可以开发出功能丰富的表格,还可以为表格定制各种显示外观、编辑特性 。

3.7.1 创建简单表格

使用JTable创建简单表格步骤:
  1. 创建一个一维数组,存储表格中每一列的标题
  1. 创建一个二维数组,存储表格中每一行数据,其中二维数组内部的每个一维数组,代表表格中的一行数据
  1. 根据第一步和第二步创建的一维数组和二维数组,创建JTable对象
  1. 把JTable添加到其他容器中显示
案例:
使用JTable实现下图效果:
演示代码:
在上面实现的简单表格中,大概有如下几个功能:
  1. 当表格高度不足以显示所有的数据行时,该表格会自动显示滚动条 。
  1. 把鼠标移动到两列之间的分界符时,鼠标形状会变成可调整大小的形状,表明用户可以自由调整表格列的大小 。
  1. 在表格列上按下鼠标并拖动时,可以将表格的整列拖动到其他位置 。
  1. 当单击某一个单元格时,系统会自动选中该单元格所在的行 。
  1. 当双击某一个单元格时,系统会自动进入该单元格的修改状态 。
JTable调整列:
JTable提供了一个setAutoResizeMode(int mode)方法用来调整表格的格式,该方法可以接收下面几个值:
  1. JTable.AUTO_RESIZE_OFF: 关闭表格的自动调整功能。当调整某一列的宽度时,其他列的宽度不会发生变化;
  1. JTable.AUTO_RESIZE_NEXT_COLUMN:只调整下一列的宽度,其他列及表格的宽度不会发生改变;
  1. JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS:平均调整当前列后面所有列的宽度,当前列的前面所有列及表格的宽度都不会发生变化,这是默认的调整方式
  1. JTable.AUTO_RESIZE_LAST_COLUMN:只调整最后一列的宽度,其他列及表格的宽度不会发生变化;
  1. JTable.AUTO_RESIZE_ALL_COLUMNS:平均调整表格中所有列的宽度,表格的宽度不会发生改变
如果需要精确控制每一列的宽度,则可通过 TableColumn 对象来实现 。 JTable 使用 TableColumn 来表示表格中的每一列, JTable 中表格列的所有属性,如最佳宽度、是否可调整宽度、最小和最大宽度等都保存在该 TableColumn 中 。 此外, TableColumn 还允许为该列指定特定的单元格绘制器和单元格编辑器(这些内容将在后面讲解) 。 TableColumn 具有如下方法 。
  1. setMaxWidth(int maxWidth): 设置该列的最大宽度 。 如果指定的 maxWidth 小于该列的最小宽度, 则 maxWidth 被设置成最小宽度 。
  1. setMinWidth(int minWidth): 设置该列的最小宽度 。
  1. setPreferredWidth(int preferredWidth): 设置该列的最佳宽度 。
  1. setResizable(boolean isResizable): 设置是否可以调整该列的 宽度 。
  1. sizeWidthToFit(): 调整该列的宽度,以适合其标题单元格的 宽度 。
JTable调整选择模式:
  1. 选则行:JTable默认的选择方式就是选择行,也可以调用setRowSelectionAllowed(boolean rowSelectionAllowed)来修改;
  1. 选择列:调用 setColumnSelectionAllowed(boolean columnSelectionAllowed)方法,修改当前JTable的选择模式为列;
  1. 选择单元格:setCellSelectionEnabled(boolean cellSelectionEnabled) ,修改当前JTable的选择模式为单元格;
JTable调整表格选择状态:
与 JList 、 JTree 类似的是, JTable 使用了 一个 ListSelectionModel 表示该表格的选择状态,程序可以 通过 ListSelectionModel 来控制 JTable 的选择模式 。 JTable 的选择模式有如下三种:
  1. ListSelectionMode.MULTIPLE_INTERVAL_SELECTION:没有任何限制,可以选择表格中任何表格单元,这是默认的选择模式 。 通过 Shi负和 Ctrl 辅助键的帮助可以选择多个表格单元 。
  1. ListSelectionMode.SINGLE_INTERVAL_SELECTION:选择单个连续区域,该选项可以选择多个表格单元,但多个表格单元之间必须是连续的 。 通过 Shift 辅助键的帮助来选择连续区域。
  1. ListSelectionMode.SINGLE_SELECTION:只能选择单个表格单元 。
案例:
通过JTable实现的宽度调整,选择模式调整,选择状态调整,实现下图效果:
演示代码:

3.7.2 TableModel和监听器

与 JList、 JTree 类似的是 , JTable 采用了 TableModel 来保存表格中的所有状态数据 : 与 ListModel类似的是, TableModel 也不强制保存该表格显示的数据 。 虽然在前面程序中看到的是直接利用 一个二维数组来创建 JTable 对象,但也可以通过 TableModel 对象来创建表格。
使用TableModel步骤:
  1. 自定义类,继承AbstractTableModel抽象类,重写下面几个方法:
  1. 创建自定义类对象,根据该对象,创建JTable对象
案例:
  1. 连接数据库,把库中所有的表名称显示到下拉列表中
  1. 点击下拉列表中某个表名时,查询数据库该表的数据,并把结果封装到TableModel中,使用JTable展示
  1. 点击表格中某个单元格,修改数据,能实时修改数据库中的数据
  1. 每修改一次数据,把修改的信息打印到下方的文本域中
演示代码:
不仅用户可以扩展 AbstractTableModel 抽象类, Swing 本身也为 AbstractTableModel 提供了 一个DefaultTableModel 实现类,程序可以通过使用 DefaultTableModel 实现类来创建 JTable 对象 。 通过DefaultTableModel 对象创建 JTable 对象后,就可以调用它提供的方法来添加数据行、插入数据行 、删除数据行和移动数据行 。 DefaultTableModel 提供了如下几个方法来控制数据行操作:

3.7.3 TableColumnModel和监听器

JTable 使用 TableColumnModel 来保存该表格所有数据列的状态数据,如果程序需要访问 JTable 的所有列状态信息,则可以通过获取该 JTable 的 TableColumnModel 来实现 。 TableColumnModel 提供了如下几个方法来增加、删除和移动数据列 :
  1. addColumn(TableColumn aColumn): 该方法用于为 TableModel 添加一列 。 该方法主要用于将原来隐藏的数据列显示出来 。
  1. moveColumn(int columnIndex, int newIndex): 该方法用于将指定列移动到其他位置 。
  1. removeColumn(TableColumn column): 该方法用于从 TableModel 中删 除指定列。实际上,该方法并未真正删除指定列,只是将该列在TableColumnModel 中隐藏起来,使之不可见 。
JTable中也提供了类似的方法完成列的操作,只是其底层依然是通过TableColumnModel来完成的。
案例:
使用DefaultTableModel和TableColumnModel完成下图效果:
演示代码:
如果程序需要监昕 JTable 里列状态的改变,例如监听列的增加、删除 、 移动等改变, 则必须使用该 JTable 所对应的 TableColumnModel 对象,该对象提供了 一个 addColumnModelListener()方法来添加监听器, 该监听器接口里包含如下几个方法 :
  1. columnAdded(TableColumnModelEvent e) : 当向 TableColumnModel 里添加数据列时将会触发该方法。
  1. columnMarginChanged(ChangeEvent e) : 当由于页面距 ( Margin ) 的改变引起列状态改变时将会触发该方法 。
  1. columnMoved(TableColumnModelEvent e): 当移动 TableColumnModel 里的数据列时将会触发该方法 。
  1. columnRemoved(TableColumnModelEvent e): 当删除 TableColumnModel 里的数据列时将会触发该方法 。
  1. columnSelectionChanged(ListSelectionEvent e): 当改变表格的选择模式时将会触发该方法。
但表格的数据列通常需要程序来控制增加、 删除 ,用户操作通常无法直接为表格增加 、删除数据列,所以使用监听器来监听 TableColumnModel 改变的情况比较少见。

3.7.4 实现列排序

使用 JTable 实现的表格并没有实现根据指定列排序的功能 , 但开发者可以利用 AbstractTableModel 类来实现该功能。由于 TableModel 不强制要求保存表格里的数据,只要 TableModel 实现了 getValueAt()、getColumnCount()和 getRowCount()三个方法, JTable 就可以根据该 TableModel 生成表格 。 因此可以创建个 SortableTableModel 实现类 , 它可以将原 TableModel 包装起来,并实现根据指定列排序的功能 。
程序创建的 SortableTableModel 实现类会对原 TableModel 进行包装,但它实际上并不保存任何数据,它会把所有的方法实现委托给原 TableModel 完成。 SortableTableModel 仅保存原 TableModel 里每行的行索引, 当程序对 SortableTableModel 的指定列排序时, 实际上仅仅对SortableTableModel 里的行索引进排序一一这样造成的结果是 : SortableTableModel 里的数据行的行索引与原 TableModel 里数据行的行索引不一致,所以对于 TableModel 的那些涉及行索引的方法都需要进行相应的转换。下面程序实现了SortableTableModel 类,并使用该类来实现对表格根据指定列排序的功能 。
案例:
实现下图功能:
双击列的头部,按照该列从小到大的顺序进行排序
演示代码:

3.7.5 绘制单元格内容

前面看到的所有表格的单元格内容都是宇符串,实际上表格的单元格内容也可以是更复杂的内容。JTable 使用 TableCellRenderer 绘制单元格, Swing 为该接口提供了 一 个实现类 : DefaultTableCellRenderer,该单元格绘制器可以绘制如下三种类型的单元格值(根据其 TableModel 的 getColurnnClass()方法来决定该单元格值的类型) :
  1. Icon: 默认的单元格绘制器会把该类型的单元格值绘制成该Icon对象所代表的图标 。
  1. Boolean: 默认的单元格绘制器会把该类型的单元格值绘制成复选按钮。
  1. Object: 默认的单元格绘制器在单元格内绘制出该对象的 toString()方法返回的字符串 。
在默认情况下,如果程序直接使用 二维数组或 Vector 来创建 JTable , 程序将会使用 JTable 的匿名内部类或 DefaultTableModel 充当该表格的 model 对象,这两个 TableModel 的 getColumnClass()方法的返回值都是 Object 。 这意味着,即使该二维数组里值的类型是 Icon , 但由于两个默认的 TableModel 实现类的 getColumnClass()方法总是返回 Object,这将导致默认的单元格绘制器把 Icon 值当成 Object 值处 理一一只是绘制出其 toString()方法返回的字符串。
为了让默认的单元格绘制器可以将 Icon 类型 的值绘制成图标,把 Boolean 类型的值绘制成复边框 ,创建 JTable 时所使用的 TableModel 绝不能采用默认的 TableModel ,必须采用扩展后的 TableModel 类,如下所示 :
提供了上面的 ExtendedTableModel 类之后 , 程序应该先创建 ExtendedTableModel 对象,再利用该对象来创建 JTable,这样就可以保证 JTable 的 model 对象的 getColumnClass()方法会返回每列真实的数据类型,默认的单元格绘制器就会将 Icon 类型的单元格值绘制成图标 , 将 Boolean 类型 的单元格值绘制成复选框。
如果希望程序采用自己定制的单元格绘制器,则必须实现自己的单元格绘制器,单元格绘制器必须实现 TableCellRenderer 接口。与前面的TreeCellRenderer 接口完全相似, 该接口里也只包含一 个getTableCellRendererComponent()方法,该该方法返回的 Component 将会作为指定单元格绘制的组件。
一旦实现了自己的单元格绘制器之后,还必须将该单元格绘制器安装到指定的 JTable 对象上,为指定的 JTable 对象安装单元格绘制器有如下两种方式 :
  1. 局部方式 ( 列级 ) : 调用 TableColumn的setCellRenderer()方法为指定列安装指定的单元格绘制器。
  1. 全局方式 (表级) :调用 JTable 的setDefaultRendererO方法为指定的 JTable 对象安装单元格绘制器, setDefaultRendererO方法需要传入两个参数,即列类型和单元格绘制器 , 表明指定类型的数据列才会使用该单元格绘制器 。
案例:
使用TableCellRenderer和TableModel实现下图效果:
演示代码:

3.7.6 编辑单元格内容

如果用户双击 JTable 表格的指定单元格,系统将会开始编辑该单元格的内容。在默认情况下,系统会使用文本框来编辑该单元格的内容,与此类似的是 ,如果用户双击 JTree 的节 点,默认也会采用文本框来编辑节点 的内容 。
但如果单元格内容不是文字内容,用户当然不希望使用文本编辑器来编辑该单元格的内容,因为这种编辑方式非常不直观,用户体验相当差 。 为了避免这种情况,可以实现自己的单元格编辑器,从而可以给用户提供更好的操作界面。
实现 JTable 的单元格编辑器应该实现 TableCellEditor 接口 ,Swing 为 TableCellEditor提供了 DefaultCellEditor 实现类,efaultCellEditor 类有三 个构造器, 它们分别使用文本框、复选框和JComboBox 作 为单元格编辑器,其中使用文本框编辑器是最常见的情形,如果单元格的值是 Boolean 类型 ,则系统默认使用复选框编辑器。如果想指定某列使用 JComboBox 作为单元格编辑器,则需要显式创建 JComboBox 实例 ,然后以此实例来创建 DefaultCellEditor 编辑器 。
使用DefaultCellEditor步骤:
  1. 自定义类,继承DefaultCellEditor,重写getTableCellEditorComponent()方法;
  1. 创建自定义类对象
  1. 为JTable安装单元格编辑器
案例:
使用TableCellEditor和TableModel完成下图效果:
演示代码:

四. 综合案例

4.1 项目概述

4.2 环境搭建

4.3 项目开发

4.4 项目打包