第24章 Kotlin与Java Swing图形用户界面编程
Kotlin目前没有自己的图形界面技术。开发人员可以借助于Java图形界面技术实现Kotlin图形界面应用程序。本章重点介绍Java Swing图形界面技术,如果你对Java Swing很熟悉可以跳过本章内容。
图形用户界面(Graphical User Interface,简称 GUI)编程对于某种语言来说非常重要。Java的应用主要方向是基于Web浏览器的应用,用户界面主要是HTML、CSS和JavaScript等基于Web的技术,这些介绍要到Java
EE阶段才能学习到。
而本章介绍的Java图形用户界面技术是基于Java
SE的Swing,事实上它们在实际应用中使用不多,因此本章的内容只做了解。
24.1 Java图形用户界面技术
Java图形用户界面技术主要有:AWT、Applet、Swing和JavaFX。
1.AWT
AWT(Abstract Window Toolkit)是抽象窗口工具包,AWT是Java 程序提供的建立图形用户界面最基础的工具集。AWT支持图形用户界面编程的功能包括:用户界面组件(控件)、事件处理模型、图形图像处理(形状和颜色)、字体、布局管理器和本地平台的剪贴板来进行剪切和粘贴等。AWT是Applet和Swing技术的基础。
AWT在实际的运行过程中是调用所在平台的图形系统,因此同样一段AWT程序在不同的操作系统平台下运行所看到的样式不同的。例如在Windows下运行,则显示的窗口是Windows风格的窗口,如图24-1所示,而在UNIX下运行时,则显示的是UNIX风格的窗口,如图24-2所示的macOS[1]是AWT窗口。
[1]macOS是苹果计算机操作系统,它也是UNIX内核。
2.Applet
Applet称为Java小应用程序,Applet基础是AWT,但它主要嵌入到HTML代码中,由浏览器加载和运行,由于存在安全隐患和运行速度慢等问题,已经很少使用了。
3.Swing
Swing是Java主要的图形用户界面技术,Swing提供跨平台的界面风格,用户可以自定义Swing的界面风格。Swing提供了比AWT更完整的组件,引入了许多新的特性。Swing API是围绕着实现AWT各个部分的API构筑的。Swing是由100%纯Java实现的,Swing组件没有本地代码,不依赖操作系统的支持,这是它与AWT组件的最大区别。本章重点介绍Swing技术。
4.JavaFX
JavaFX是开发丰富互联网应用程序(Rich Internet Application,缩写RIA)的图形用户界面技术,JavaFX期望能够在桌面应用的开发领域与Adobe公司的AIR、微软公司的Silverlight相竞争。传统的互联网应用程序基于Web的,客户端是浏览器。而丰富互联网应用程序试图打造自己的客户端,替代浏览器。
24.2 Swing技术基础
AWT是Swing的基础,Swing事件处理和布局管理都是依赖于AWT,AWT内容来自java.awt包,Swing内容来自javax.swing包。AWT和Swing作为图形用户界面技术包括了4个主要的概念:组件(Component)、容器(Container)、事件处理和布局管理器(LayoutManager),下面将围绕这些概念展开。
24.2.1 Swing类层次结构
容器和组件构成了Swing的主要内容,下面分别介绍一下Swing中容器和组件类层次结构。
图24-3所示是Swing容器类层次结构,Swing容器类主要有:JWindow、JFrame和JDialog,其他的不带“J”开头都是AWT提供的类,在Swing中大部分类都是以“J”开头。
图24-4所示是Swing组件类层次结构,Swing所有组件继承自JComponent,JComponent又间接继承自AWT的java.awt.Component类。Swing组件很多,这里不一一解释了,在后面的学习过程中会重点介绍一下组件。
24.2.2 Swing程序结构
图形用户界面主要是由窗口以及窗口中的组件构成的,编写Swing程序主要就是创建窗口和添加组件过程。Swing中的窗口主要是使用JFrame,很少使用JWindow。JFrame有标题、边框、菜单、大小和窗口管理按钮等窗口要素,而JWindow没有标题栏和窗口管理按钮。
构建Swing程序主要有两种方式:创建JFrame或继承JFrame。下面通过一个示例介绍一下这两种方式如何实现,该示例运行效果如图24-5所示,窗口标题是MyFrame,窗口中有显示字符串“Hello Swing!”。
1.创建JFrame方式
创建JFrame方式就是直接实例化JFrame对象,然后设置JFrame属性,添加窗口所需要的组件。
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/SwingDemo1.kt
package com.a51work6.section2
import javax.swing.JFrame
import javax.swing.JLabel
fun main(args: Array) {
// 创建窗口对象
val frame = JFrame(“MyFrame”) ①
// 创建Label
val label = JLabel("Hello Swing!") ②
// 获得窗口的内容面板
val pane = frame.contentPane ③
// 添加Label到内容面板
pane.add(label) ④
// 设置窗口大小
frame.setSize(300, 300) ⑤
// 设置窗口可见
frame.isVisible = true ⑥
}
上述代码第①行使用JFrame的JFrame(title:
String!)构造函数创建JFrame对象,title是设置创建的标题。默认情况下JFrame是没有大小且不可见的,因此创建JFrame对象后还需要设置大小和可见,代码第⑤行是设置窗口大小,代码第⑥行是设置窗口的可见。
创建好窗口后,就需要将其中的组件添加进来,代码第②行是创建标签对象,构造函数中字符串参数是标签要显示的文本。创建好组件之后需要把它添加到窗口的内容面板上,代码第③行是获得窗口的内容面板,它是Container容器类型。代码第④行调用容器的add函数将组件添加到窗口上。
2.继承JFrame方式
继承JFrame方式就是编写一个继承JFrame的子类,在构造函数中初始化窗口,添加窗口所需要的组件。
自定义窗口代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/MyFrame.kt
package com.a51work6.section2
import javax.swing.JLabel
import javax.swing.JFrame
class MyFrame(title: String) : JFrame(title) { ①
init {
// 创建Label
val label =JLabel("Hello Swing!")
// 获得窗口的内容面板
val pane =contentPane
// 添加Label到内容面板
pane.add(label)
// 设置窗口大小
setSize(300, 300)
// 设置窗口可见
isVisible =true
}
}
上述代码第①行是声明MyFrame继承JFrame,并声明主构造函数,参数是窗口标题。
调用代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/SwingDemo2.kt
package com.a51work6.section2
fun main(args: Array) {
//创建Frame对象
MyFrame(“MyFrame”)
}
运行上述代码可见继承JFrame方式和创建JFrame方式效果完全一样。
24.3 事件处理模型
图形界面的组件要响应用户操作,就必须添加事件处理机制。Swing采用AWT的事件处理模型进行事件处理。在事件处理的过程中涉及三个要素:
1.事件。是用户对界面的操作,在Java中事件被封装称为事件类java.awt.AWTEvent及其子类,例如按钮单击事件类是java.awt.event.ActionEvent。
2.事件源。是事件发生的场所,就是各个组件,例如按钮单击事件的事件源是按钮(Button)。
3.事件处理者。是事件处理程序,在Java中事件处理者是实现特定接口的事件对象。
在事件处理模型中最重要的是事件处理者,它根据事件(假设XXXEvent事件)的不同会实现不同的接口,这些接口命名为XXXListener,所以事件处理者也称为事件监听器。最后事件源通过addXXXListener函数添加事件监听,监听XXXEvent事件。各种事件和对应的监听器接口,如表24-1所示。
事件处理者可以是实现了XXXListener接口任何形式,即:一般类、内部类、对象表达式和Lambda表达式等;如果XXXListener接口只有一个抽象函数,事件处理者还可以是Lambda表达式。为了访问窗口中的组件方便,往往使用内部类、对象表达式和Lambda表达式情况很多。
24.3.1 内部类和对象表达式处理事件
内部类和对象表达式能够方便访问窗口中的组件,本节先介绍内部类和对象表达式实现的事件监听器。
下面通过一个示例介绍采用内部类和对象表达式实现的事件处理模型,如图24-8所示的示例,界面中有两个按钮和一个标签,当单击Button1或Button2时会改变标签显示的内容。
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/s1/MyFrame.kt
package com.a51work6.section3.s1
import java.awt.BorderLayout
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
class MyFrame(title: String) : JFrame(title) {
// 创建标签
private val label = JLabel(“Label”) ①
init {
// 创建Button1
val button1= JButton("Button1")
// 创建Button2
val button2= JButton("Button2")
// 注册事件监听器,监听Button1单击事件
button1.addActionListener(object : ActionListener { ②
override fun actionPerformed(event: ActionEvent) {
label.text = “Hello Swing!”
}
})
// 注册事件监听器,监听Button2单击事件
button2.addActionListener(ActionEventHandler()) ③
// 添加标签到内容面板
contentPane.add(label,BorderLayout.NORTH) ④
// 添加Button1到内容面板
contentPane.add(button1, BorderLayout.CENTER)
// 添加Button2到内容面板
contentPane.add(button2, BorderLayout.SOUTH)
// 设置窗口大小
setSize(350, 120)
// 设置窗口可见
isVisible =true
}
// Button2事件处理者
inner class
ActionEventHandler : ActionListener { ⑤
override
fun actionPerformed(e: ActionEvent) {
label.text = “Hello World!”
}
}
}
上述代码第④行通过contentPane.add(label, BorderLayout.NORTH)函数将标签添加到内容面板,这个add函数与前面介绍的有所不同,它的第二个参数是指定组件的位置,有关布局管理的内容,将在24.4节详细介绍。
代码第②行和第③行都是注册事件监听器监听Button的单击事件(ActionEvent),代码第②行的事件监听器是一个对象表达式,代码第⑤行的事件监听器是一个内部类,它们都实现了ActionEventHandler接口。
24.3.2 Lambda表达式处理事件
如果一个事件监听器接口只有一个抽象函数,这种接口称为SAM(21.2.4节)接口,SAM…接口实现可以使用Lambda表达式,SAM接口主要有:ActionListener、AdjustmentListener、ItemListener、MouseWheelListener、TextListener和WindowStateListener等。
下面将24.3.2节的示例修改一下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section3/s2/MyFrame.kt
package com.a51work6.section3.s2
import java.awt.BorderLayout
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
class MyFrame(title: String) : JFrame(title), ActionListener { ①
// 创建标签
private val label = JLabel(“Label”)
init {
// 创建Button1
val button1= JButton("Button1")
// 创建Button2
val button2= JButton("Button2")
// 注册事件监听器,监听Button1单击事件
button1.addActionListener { label.text = “Hello Swing!” } ②
// 注册事件监听器,监听Button2单击事件
button2.addActionListener(this) ③
// 添加标签到内容面板
contentPane.add(label, BorderLayout.NORTH)
// 添加Button1到内容面板
contentPane.add(button1, BorderLayout.CENTER)
// 添加Button2到内容面板
contentPane.add(button2, BorderLayout.SOUTH)
// 设置窗口大小
setSize(350, 120)
// 设置窗口可见
isVisible =true
}
ov