《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程

第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内核。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第1张图片
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第2张图片
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”开头。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第3张图片
图24-4所示是Swing组件类层次结构,Swing所有组件继承自JComponent,JComponent又间接继承自AWT的java.awt.Component类。Swing组件很多,这里不一一解释了,在后面的学习过程中会重点介绍一下组件。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第4张图片
24.2.2 Swing程序结构
图形用户界面主要是由窗口以及窗口中的组件构成的,编写Swing程序主要就是创建窗口和添加组件过程。Swing中的窗口主要是使用JFrame,很少使用JWindow。JFrame有标题、边框、菜单、大小和窗口管理按钮等窗口要素,而JWindow没有标题栏和窗口管理按钮。
构建Swing程序主要有两种方式:创建JFrame或继承JFrame。下面通过一个示例介绍一下这两种方式如何实现,该示例运行效果如图24-5所示,窗口标题是MyFrame,窗口中有显示字符串“Hello Swing!”。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第5张图片
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函数将组件添加到窗口上。
在这里插入图片描述
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第6张图片
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第7张图片
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第8张图片
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表达式情况很多。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第9张图片
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第10张图片
24.3.1 内部类和对象表达式处理事件
内部类和对象表达式能够方便访问窗口中的组件,本节先介绍内部类和对象表达式实现的事件监听器。
下面通过一个示例介绍采用内部类和对象表达式实现的事件处理模型,如图24-8所示的示例,界面中有两个按钮和一个标签,当单击Button1或Button2时会改变标签显示的内容。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第11张图片
示例代码如下:
//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
}

override fun actionPerformed(event: ActionEvent) {          ④
    label.text= "Hello Swing!"
}

}
上述代码第②行采用Lambda表达式实现的事件监听器,可见代码非常简单。另外,当前窗口本身也可以是事件处理者,代码第①行声明窗口实现ActionListener接口,代码第④行是实现抽象函数,那么注册事件监听器参数就是this了,见代码第③行。

24.3.3 使用适配器
事件监听器都是接口,在Kotlin中接口中定义的抽象函数必须全部是实现,哪怕你对某些函数并不关心,你也要给一对空的大括号表示实现。例如WindowListener是窗口事件(WindowEvent)监听器接口,为了在窗口中接收到窗口事件,需要在窗口中注册WindowListener事件监听器,示例代码如下:
this.addWindowListener(object : WindowListener {
override fun windowActivated(e: WindowEvent) {
}
override fun windowClosed(e: WindowEvent) {}
override fun windowClosing(e: WindowEvent) { ①
// 退出系统
System.exit(0)
}
override fun windowDeactivated(e: WindowEvent) {}
override fun windowDeiconified(e: WindowEvent) {}
override fun windowIconified(e: WindowEvent) {}
override fun windowOpened(e: WindowEvent) {}
})
实现WindowListener接口需要提供它的7个函数的实现,很多情况下只需要实现其中一两个函数,本例是关闭窗口只需要实现代码第①行的windowClosing函数,其他的函数并不关心,但是也必须给出空的实现。这样的代码看起来很臃肿,为此AWT提供了一些与监听器相配套的适配器。监听器是接口,命名采用XXXListener,而适配器是类,命名采用XXX Adapter。在使用时通过继承事件所对应的适配器类,覆盖所需要的函数,无关函数不用实现。
采用适配器注册接收窗口事件代码如下:
this.addWindowListener(object : WindowAdapter() {
override fun windowClosing(e: WindowEvent) {
// 退出系统
System.exit(0)
}
})
可见代码非常的简洁。事件适配器提供了一种简单的实现监听器的手段, 可以缩短程序代码。但是,由于Kotlin的单一继承机制,当需要多种监听器或此类已有父类时,就无法采用事件适配器了。
并非所有的监听器接口都有对应的适配器类,一般定义了多个函数的监听器接口,例如WindowListener有多个函数对应多种不同的窗口事件时,才需要配套的适配器,主要的适配器如下:
o ComponentAdapter。组件适配器。
o ContainerAdapter。容器适配器。
o FocusAdapter。焦点适配器。
o KeyAdapter。键盘适配器。
o MouseAdapter。鼠标适配器。
o MouseMotionAdapter。鼠标运动适配器。
o WindowAdapter。窗口适配器。

24.4 布局管理

在Swing图形用户界面中容器内的所有组件布局交给布局管理器管理的。布局管理器负责组件的排列顺序、大小、位置,以及当窗口移动或调整大小后组件如何变化等。
Swing提供了7种布局管理器包括:FlowLayout、BorderLayout、GridLayout、BoxLayout、CardLayout、SpringLayout和GridBagLayout,其中最基础的是FlowLayout、BorderLayout和GridLayout布局管理器,下面重点介绍这三种布局。

24.4.1 FlowLayout布局
FlowLayout布局摆放组件的规律是:从上到下、从左到右进行摆放组件,如果容器足够宽,第一个组件先添加到容器中第一行的最左边,后续的组件依次添加到上一个组件的右边,如果当前行已摆放不下该组件,则摆放到下一行的最左边。
FlowLayout主要的构造函数如下:
o FlowLayout(align: Int, hgap: Int, vgap: Int)。创建一个FlowLayout对象,它具有指定的对齐方式以及指定的水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙,单位是像素。
o FlowLayout(align: Int)。创建一个FlowLayout对象,指定的对齐方式,默认的水平和垂直间隙是5个像素。
o FlowLayout()。创建一个FlowLayout对象,它是居中对齐的,默认的水平和垂直间隙是5个像素。
上述参数align是对齐方式,它是通过FlowLayout的常量指定的,这些常量说明如下:
o FlowLayout.CENTER。指示每一行组件都应该是居中的。
o FlowLayout.LEADING。指示每一行组件都应该与容器方向的开始边对齐,例如,对于从左到右的方向,则与左边对齐。
o FlowLayout.LEFT。指示每一行组件都应该是左对齐的。
o FlowLayout.RIGHT。指示每一行组件都应该是右对齐的。
o FlowLayout.TRAILING。指示每行组件都应该与容器方向的结束边对齐,例如,对于从左到右的方向,则与右边对齐。

示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section4/s1/MyFrame.kt
package com.a51work6.section4.s1

import java.awt.FlowLayout
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel

class MyFrame(title: String) : JFrame(title) {

// 声明标签
private val label: JLabel

init {

    layout =FlowLayout(FlowLayout.LEFT, 20, 20)           ①
    // 创建标签
    label =JLabel("Label")
    // 添加标签到内容面板       

contentPane.add(label) ②

    // 创建Button1
    val button1= JButton("Button1")
    // 添加Button1到内容面板       

contentPane.add(button1) ③

    // 创建Button2
    val button2= JButton("Button2")
    // 添加Button2到内容面板    

contentPane.add(button2) ④

    // 设置窗口大小       

setSize(350, 120)
// 设置窗口可见
isVisible =true

    // 注册事件监听器,监听Button2单击事件       

button2.addActionListener { label.text = “Hello Swing!” }
// 注册事件监听器,监听Button1单击事件
button1.addActionListener { label.text = “Hello World!” }
}
}
上述代码第①行是设置当前窗口的布局是FlowLayout布局,采用FlowLayout(align: Int, hgap: Int, vgap: Int)构造函数。一旦设置了FlowLayout布局,就可以通过add函数添加组件到窗口的内容面板,见代码第②行、第③行和第④行。
运行结果如图24-9(a)所示。采用FlowLayout布局如果水平空间比较小,组件会垂直摆放,拖曳窗口的边缘使窗口变窄,如图24-9(b)所示,最后一个组件换行了。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第12张图片
24.4.2 BorderLayout布局
BorderLayout布局是窗口的默认布局管理器,前面24.3节的示例就是采用BorderLayout布局实现。
BorderLayout 是JWindow、JFrame和JDialog的默认布局管理器。BorderLayout布局管理器把容器分成5个区域:北、南、东、西和中,如图24-10所示每个区域只能放置一个组件。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第13张图片
BorderLayout主要的构造函数如下:
o BorderLayout( hgap: Int, vgap: Int)。创建一个BorderLayout对象,指定水平和垂直间隙,hgap参数是组件之间的水平间隙,vgap参数是组件之间的垂直间隙,单位是像素。
o BorderLayout()。创建一个BorderLayout对象,组件之间没有间隙。
BorderLayout布局有5个区域,为此BorderLayout中定义了5个约束常量,说明如下:
o BorderLayout.CENTER。中间区域的布局约束(容器中央)。
o BorderLayout.EAST。东区域的布局约束(容器右边)。
o BorderLayout.NORTH。北区域的布局约束(容器顶部)。
o BorderLayout.SOUTH。南区域的布局约束(容器底部)。
o BorderLayout.WEST。西区域的布局约束(容器左边)。

示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/s2/MyFrame.kt
package com.a51work6.section4.s2

import java.awt.BorderLayout
import java.awt.Button
import javax.swing.JFrame

class MyFrame(title: String) : JFrame(title) {

init {
    // 设置BorderLayout布局
    layout =BorderLayout(10, 10)               ①
    
    // 添加按钮到容器的North区域       

contentPane.add(Button(“NORTH”), BorderLayout.NORTH) ②
// 添加按钮到容器的South区域
contentPane.add(Button(“SOUTH”), BorderLayout.SOUTH) ③
// 添加按钮到容器的East区域
contentPane.add(Button(“EAST”), BorderLayout.EAST) ④
// 添加按钮到容器的West区域
contentPane.add(Button(“WEST”), BorderLayout.WEST) ⑤
// 添加按钮到容器的Center区域
contentPane.add(Button(“CENTER”), BorderLayout.CENTER) ⑥

setSize(300, 300)
isVisible =true
}
}
上述代码第①行设置窗口布局为BorderLayout布局,组件之间间隙是10个像素,事实上窗口默认布局就是BorderLayout,只是组件之间没有间隙,如图24-11所示。代码第②行~第⑥行分别添加了5个按钮,使用add函数添加,第一个参数是要添加的组件;第二个参数是指定约束。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第14张图片
当使用BorderLayout时,如果容器的大小发生变化,其变化规律为:组件的相对位置不变,大小发生变化。如图24-12所示,如果容器变高或矮,则North和South不变,West、Center和East变高或矮;如果容器变宽或窄,West和East区域不变,North、Center和South变宽或窄。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第15张图片
另外,在5个区域中不一定都放置了组件,如果某个区域缺少组件,界面布局会有比较大的影响,具体影响如图24-13所示,图中列出了主要的一些情况。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第16张图片
24.4.3 GridLayout布局
GridLayout布局以网格形式对组件进行摆放,容器被分成大小相等的矩形,一个矩形中放置一个组件。
GridLayout布局主要的构造函数如下:
o GridLayout()。创建具有默认值的GridLayout对象,即每个组件占据一行一列。
o GridLayout(rows: Int, cols: Int)。创建具有指定行数和列数的GridLayout对象。
o GridLayout(rows: Int, cols: Int, hgap: Int, vgap: Int)。创建具有指定行数和列数的GridLayout对象,并指定水平和垂直间隙。

示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/s3/MyFrame.kt
package com.a51work6.section4.s3

import java.awt.Button
import java.awt.GridLayout
import javax.swing.JFrame

class MyFrame(title: String) : JFrame(title) {

init {

    // 设置3行3列的GridLayout布局管理器
    layout = GridLayout(3, 3)              ①
    
    // 添加按钮到第一行的第一格       

contentPane.add(Button(“1”)) ②
// 添加按钮到第一行的第二格
contentPane.add(Button(“2”))
// 添加按钮到第一行的第三格
contentPane.add(Button(“3”))
// 添加按钮到第二行的第一格
contentPane.add(Button(“4”))
// 添加按钮到第二行的第二格
contentPane.add(Button(“5”))
// 添加按钮到第二行的第三格
contentPane.add(Button(“6”))
// 添加按钮到第三行的第一格
contentPane.add(Button(“7”))
// 添加按钮到第三行的第二格
contentPane.add(Button(“8”))
// 添加按钮到第三行的第三格
contentPane.add(Button(“9”)) ③

    setSize(400, 400)
    isVisible = true
}

}
上述代码第①行是设置当前窗口布局采用3行3列的GridLayout布局,它有一个9个区域,分别从左到右,从上到下摆放。代码第②行~第③行添加了9个Button。运行结果如图24-14所示。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第17张图片
GridLayout布局将容器分成几个区域,也会出现某个区域缺少组件情况,GridLayout布局会根据行列划分的不同,会平均占据容器的空间,实际情况比较复杂。图24-15中列出了一些主要情况。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第18张图片
24.4.4 不使用布局管理器
如果要开发的图形用户界面应用不考虑跨平台,不考虑动态布局,窗口大小不变的,那么布局管理器就失去使用的意义。容器也可以不设置布局管理器,那么此时的布局是由开发人员自己管理的。
组件有三个与布局有关的函数setLocation、setSize和setBounds,在设置了布局管理的容器中组件的这几个函数不起作用的,不设置布局管理时它们才起作用的。
这三个函数的说明如下:
o setLocation(x: Int, y: Int)函数是设置组件的位置。
o setSize(width: Int, height: Int)函数是设置组件的大小。
o setBounds(x: Int, y: Int , width: Int, height: In)函数是设置组件的大小和位置。
下面通过示例介绍一下不使用布局管理器情况,如图24-16所示的界面。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第19张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/s3/MyFrame.kt
package com.a51work6.section4.s4

import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.SwingConstants

class MyFrame(title: String) : JFrame(title) {

init {

    //设置窗口大小不变的
    isResizable= false                       ①
    
    // 不设置布局管理器
    layout =null              ②
    
    // 创建标签
    val label =JLabel("Label")
    // 设置标签的位置和大小      

label.setBounds(89, 13, 100, 30) ③
// 设置标签文本水平居中
label.horizontalAlignment = SwingConstants.CENTER ④
// 添加标签到内容面板
contentPane.add(label)

    // 创建Button1
    val button1= JButton("Button1")
    // 设置Button1的位置和大小       

button1.setBounds(89, 59, 100, 30) ⑤
// 添加Button1到内容面板
contentPane.add(button1)

    // 创建Button2
    val button2= JButton("Button2")
    // 设置Button2的位置       

button2.setLocation(89, 102) ⑥
// 设置Button2的大小
button2.setSize(100, 30) ⑦
// 添加Button2到内容面板
contentPane.add(button2)

    // 设置窗口大小       

setSize(300, 200)
// 设置窗口可见
isVisible =true

    // 注册事件监听器,监听Button2单击事件       

button2.addActionListener { label.text = “Hello Swing!” }

    // 注册事件监听器,监听Button1单击事件       

button1.addActionListener { label.text = “Hello World!” }
}
}
上述代码第①行是设置不能调整窗口大小,没有设置布局管理器后,容器中的组件都绝对布局,当容器大小如果变化,那么其中的组件大小和位置都不会变化,如图24-17所示,将窗口拉大后,组件还是在原来的位置。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第20张图片
代码第②行layout属性设置管理器,layout= null是不设置布局管理器。
代码第③行和第⑤行是通过调用setBounds函数设置组件的大小和位置。也可以分别调用setLocation和setSize函数设置组件的大小和位置,实现与setBounds函数相同的效果,见代码第⑥行和第⑦行。
另外,代码第④行horizontalAlignment属性设置了标签的文本水平居中。

24.5 Swing组件

Swing所有组件都继承自JComponent,主要有文本处理、按钮、标签、列表、面板、组合框、滚动条、滚动面板、菜单、表格和树等组件。下面介绍一下常用的组件。

24.5.1 标签和按钮
标签和按钮在前面示例中已经用到了,本节再深入地介绍一下它们。
Swing中标签类是JLabel,它不仅可以显示文本还可以显示图标,JLabel的构造函数如下:
o JLabel()。创建一个无图标无标题标签对象。
o JLabel(image: Icon!)。创建一个具有图标的标签对象。
o JLabel(image: Icon!, horizontalAlignment: Int)。通过指定图标和水平对齐方式创建标签对象。
o JLabel(text: String!)。创建一个标签对象,并指定显示的文本。
o JLabel(text: String!, icon: Icon!, horizontalAlignment: Int)。通过指定显示的文本、图标和水平对齐方式创建标签对象。
o JLabel(text: String!, horizontalAlignment: Int)。通过指定显示的文本和水平对齐方式创建标签对象。
上述构造函数horizontalAlignment参数是水平对齐方式,它的取值是SwingConstants 中定义的以下常量之一:LEFT、CENTER、RIGHT、LEADING 或 TRAILING。
Swing中的按钮类是JButton,JButton不仅可以显示文本还可以显示图标,JButton常用的构造函数:
o JButton()。创建不带文本或图标的按钮对象。
o JButton(icon : Icon!)。创建一个带图标的按钮对象。
o JButton(text: String!)。创建一个带文本的按钮对象。
o JButton(text: String!, icon : Icon!)。创建一个带初始文本和图标的按钮对象。
下面通过示例介绍一下标签和按钮中使用图标,示例如图24-18所示的界面,界面中上面图标是标签,下面两个图标是按钮,当单击按钮时标签可以切换图标。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第21张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s1/MyFrame.kt
package com.a51work6.section5.s1

import javax.swing.*

class MyFrame(title: String) : JFrame(title) {

// 用于标签切换的图标
private val images = arrayOf(           

ImageIcon("./icon/0.png"),
ImageIcon("./icon/1.png"),
ImageIcon("./icon/2.png"),
ImageIcon("./icon/3.png"),
ImageIcon("./icon/4.png"),
ImageIcon("./icon/5.png")) ①

// 当前页索引
private var currentPage = 0                          ②

init {

    // 设置窗口大小不变的
    isResizable= false
    
    // 不设置布局管理器
    layout =null                       ③
    
    // 创建标签
    val label =JLabel(images[0])
    // 设置标签的位置和大小       

label.setBounds(94, 27, 100, 50)
// 设置标签文本水平居中
label.horizontalAlignment = SwingConstants.CENTER
// 添加标签到内容面板
contentPane.add(label)

    // 创建向后翻页按钮
    val backButton = JButton(ImageIcon("./icon/ic_menu_back.png"))④
    // 设置按钮的位置和大小       

backButton.setBounds(77, 90, 47, 30)
// 添加按钮到内容面板
contentPane.add(backButton)

    // 创建向前翻页按钮
 val 
 forwardButton =JButton(ImageIcon("./icon/ic_menu_forward.png"))⑤
    // 设置按钮的位置和大小       

forwardButton.setBounds(179, 90, 47, 30)
// 添加按钮到内容面板
contentPane.add(forwardButton)

    // 设置窗口大小       

setSize(300, 200)
// 设置窗口可见
isVisible =true

    // 注册事件监听器,监听向后翻页按钮单击事件       

backButton.addActionListener {
if (currentPage < images.size - 1) { currentPage++
}
label.icon = images[currentPage]
}

    // 注册事件监听器,监听向前翻页按钮单击事件       

forwardButton.addActionListener {
if(currentPage > 0) {
currentPage–
}
label.icon = images[currentPage]
}
}
}
上述代码第①行定义ImageIcon数组,用于标签切换图标,注意Icon是接口,ImageIcon是实现Icon接口。代码第②行currentPage变量记录了当前页索引,前后翻页按钮会改变前页索引。
代码第③行是不设置布局管理器。代码第④行和第⑤行是创建向后翻页按钮,构造函数参数是ImageIcon对象。

24.5.2 文本输入组件
文本输入组件主要有:文本框(JTextField)、密码框(JPasswordField)和文本区(JTextArea)。文本框和密码框都只能输入和显示单行文本。当按下Enter键时,可以触发ActionEvent事件。而文本区可以输入和显示多行多列文本。
文本框(JTextField)常用的构造函数:
o JTextField()。创建一个空的文本框对象。
o JTextField(columns: Int)。指定列数,创建一个空的文本框对象,列数是文本框显示的宽度,列数主要用于FlowLayout布局。
o JTextField(text: String)。创建文本框对象,并指定初始化文本。
o JTextField(text: String, columns: Int)。创建文本框对象,并指定初始化文本和列数。
JPasswordField继承自JTextField构造函数类似,这里不再赘述。
文本区(JTextArea)常用的构造函数:
o JTextArea()。创建一个空的文本区对象。
o JTextArea(rows : Int, columns: Int)。创建文本区对象,并指定行数和列数。
o JTextArea(text : String!)。创建文本区对象,并指定初始化文本。
o JTextArea(text : String!, rows : Int, int columns: Int)。创建文本区对象,并指定初始化文本、行数和列数。
下面通过示例介绍一下文本输入组件,示例如图24-19所示的界面,界面中有三个标签(TextField:、Password:和TextArea:),一个文本框、一个密码框和一个文本区。这个布局有点复杂,可以采用布局嵌套,如图24-20所示,将TextField:标签、Password:标签、文本框和密码框都放到一个面板(panel1)中;将TextArea:和文本区放到一个面板(panel2)中。两个面板panel1和panel2放到内容视图中,内容视图采用BorderLayout布局,每个面板内部采用FlowLayout布局。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第22张图片
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第23张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s2/MyFrame.kt
package com.a51work6.section5.s2

import java.awt.BorderLayout
import javax.swing.*

class MyFrame(title: String) : JFrame(title) {

private val textField: JTextField
private val passwordField: JPasswordField

init {
    // 创建一个面板panel1放置TextField和Password
    val panel1 = JPanel()                                              ①
    // 将面板panel1添加到内容视图       

contentPane.add(panel1, BorderLayout.NORTH) ②

    // 创建标签
    val lblTextFieldLabel = JLabel("TextField:") 
    // 添加标签到面板panel1       

panel1.add(lblTextFieldLabel)

    // 创建文本框
    textField =JTextField(12)                      ③
    // 添加文本框到面板panel1       

panel1.add(textField)

    // 创建标签
    val lblPasswordLabel = JLabel("Password:")
    // 添加标签面板panel1       

panel1.add(lblPasswordLabel)

    // 创建密码框       

passwordField = JPasswordField(12) ④
// 添加密码框到面板panel1
panel1.add(passwordField)

    // 创建一个面板panel2放置TextArea
    val panel2= JPanel()                             ⑤       

contentPane.add(panel2, BorderLayout.SOUTH) ⑥

    // 创建标签
    val lblTextAreaLabel =JLabel("TextArea:")
    // 添加标签到面板panel2       

panel2.add(lblTextAreaLabel)

    // 创建文本区
    val textArea = JTextArea(3, 20)              ⑦
    // 添加文本区到面板panel2       

panel2.add(textArea)

    // 设置窗口大小
    pack()    // 紧凑排列,其作用相当于setSize()         ⑧
    
    // 设置窗口可见
    isVisible =true       

textField.addActionListener { textArea.text = “在文本框上按下Enter键” } ⑨
}
}
上述代码第①行和第⑤行是创建面板容器,面板(JPanel)是一种没有标题栏和边框的容器,经常用于嵌套布局。然后再将这两个面板,添加到内容视图中,见代码第②行和第⑥行。
代码第③行创建文本框对象,指定列数是12。代码第④行是创建密码框,指定列数是12。它们都添加到面板panel1中。
代码第⑦行创建文本区对象,指定行数为3,列数为20,并将其添加到面板panel2中。
代码第⑧行pack函数是设置窗口的大小,它设置的大小是将容器中所有组件刚好包裹进去。
代码第⑨行是文本框textField注册ActionEvent事件,当用户在文本框中按下Enter键时触发。

24.5.3 复选框和单选按钮
Swing中提供了用于多选和单选功能的组件。
多选组件是复选框(JCheckBox),复选框(JCheckBox)有时也单独使用,能提供两种状态的开和关。
单选组件是单选按钮(JRadioButton),同一组的多个单选按钮应该具有互斥特性,这也是为什么单选按钮也叫做收音机按钮(RadioButton),就是当一个按钮按下时,其他按钮一定抬起。同一组多个单选按钮应该放到同一个ButtonGroup对象,ButtonGroup对象不属于容器,它会创建一个互斥作用范围。
JCheckBox主要构造函数如下:
o JCheckBox()。创建一个没有文本、没有图标并且最初未被选定的复选框对象。
o JCheckBox(icon : Icon!)。创建有一个图标、最初未被选定的复选框对象。
o JCheckBox(icon : Icon!, selected : boolean)。创建一个带图标的复选框对象,并指定其最初是否处于选定状态。
o JCheckBox(text: String)。创建一个带文本的、最初未被选定的复选框对象。
o JCheckBox(text: String, selected : boolean)。创建一个带文本的复选框对象,并指定其最初是否处于选定状态。
o JCheckBox(text: String, icon : Icon!)。创建带有指定文本和图标的、最初未被选定的复选框对象。
o JCheckBox(text: String, icon : Icon!, selected : boolean)。创建一个带文本和图标的复选框对象,并指定其最初是否处于选定状态。
JCheckBox和JRadioButton它们有着相同的父类JToggleButton,有着相同函数和类似的构造函数,因此JRadioButton构造函数这里不再赘述。
下面通过示例介绍一下复选框和单选按钮,示例如图24-21所示的界面,界面中有一组复选框和一组单选按钮。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第24张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section2/s3/MyFrame.kt
package com.a51work6.section5.s3

class MyFrame(title: String) : JFrame(title),
ItemListener { ①

//声明并创建RadioButton对象
private val radioButton1 = JRadioButton("男")         ②
private val radioButton2 = JRadioButton("女")         ③

init {

    // 创建一个面板panel1放置TextField和Password
    val panel1= JPanel()
    val flowLayout1 = panel1.layout as FlowLayout       

flowLayout1.alignment = FlowLayout.LEFT
// 将面板panel1添加到内容视图
contentPane.add(panel1, BorderLayout.NORTH)

    // 创建标签
    val lblTextFieldLabel = JLabel("选择你喜欢的编程语言:")
    // 添加标签到面板panel1       

panel1.add(lblTextFieldLabel)

    //创建checkBox1对象
    val checkBox1 = JCheckBox("Java")                  ④
    //添加checkBox1到面板panel1       

panel1.add(checkBox1)

    val checkBox2 = JCheckBox("C++")
    //添加checkBox2到面板panel1       

panel1.add(checkBox2)
val checkBox3 = JCheckBox(“Objective-C”)
//注册checkBox3对ActionEvent事件监听

checkBox3.addActionListener { ⑤
// 打印checkBox3状态
println(checkBox3.isSelected)
}
//添加checkBox3到面板panel1
panel1.add(checkBox3)

    // 创建一个面板panel2放置TextArea
    val panel2= JPanel()
    val flowLayout2 = panel2.layout as FlowLayout
    flowLayout2.alignment= FlowLayout.LEFT       

contentPane.add(panel2, BorderLayout.SOUTH)

    // 创建标签
    val lblTextAreaLabel = JLabel("选择性别:")
    // 添加标签到面板panel2       

panel2.add(lblTextAreaLabel)

    //创建ButtonGroup对象
    val buttonGroup = ButtonGroup()                    ⑥
    //添加RadioButton到ButtonGroup对象       

buttonGroup.add(radioButton1)
buttonGroup.add(radioButton2)

    //添加RadioButton到面板panel2                     ⑦       

panel2.add(radioButton1)
panel2.add(radioButton2)

    //注册ItemEvent事件监听器       

radioButton1.addItemListener(this) ⑧
radioButton2.addItemListener(this)

    // 设置窗口大小
    pack() // 紧凑排列,其作用相当于setSize()
    
    // 设置窗口可见
    isVisible =true
}   

//实现ItemListener接口方法
override fun itemStateChanged(e: ItemEvent) {       ⑨
    if (e.stateChange == ItemEvent.SELECTED) {    ⑩
        val button = e.item as JRadioButton           

println(button.text)
}
}
}
上述代码第②行和第③行创建了两个单选按钮对象,为了能让这两单选按钮互斥,则需要把它们添加到一个ButtonGroup对象,见代码第⑥行创建ButtonGroup对象,并把它们添加进来。为了监听两单选按钮的选择状态,注册ItemEvent事件监听器,见代码第⑧行,为了一起处理两个单选按钮事件,它们需要使用同一个事件处理者,本例是this,这说明当前窗口是事件处理者,它实现了ItemListener接口,见代码第①行。代码第⑨行实现了ItemListener接口的抽象函数。两个单选按钮使用同一个事件处理者,那么如何判断是哪一个按钮触发的事件呢?代码第⑩行是判断按钮是否被选中,如果选中通过e.getItem()函数获得按钮引用,然后再通过getText()函数获得按钮的文本标签。
代码第④行是创建了一个复选框对象,并且应该把它添加到面板panel1中。复选框和单选按钮都属于按钮,也能响应ActionEvent事件,代码第⑤行是注册checkBox3对ActionEvent事件监听。

24.5.4 下拉列表
Swing中提供了下拉列表(JComboBox)组件,每次只能选择其中的一项。
JComboBox常用的构造函数有:
o JComboBox()。创建一个下拉列表对象。
o JComboBox(items: Array)。创建一个下拉列表对象,items设置下拉列表中选项。下拉列表中选项内容可以是任意类,而不再局限于String。
下面通过示例介绍一下拉列表组件,示例如图24-22所示的界面,界面中有;两个下拉列表组件。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第25张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s4/MyFrame.kt
package com.a51work6.section5.s4

import java.awt.GridLayout
import java.awt.event.ItemEvent
import javax.swing.JComboBox
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.SwingConstants

class MyFrame(title: String) : JFrame(title) {

private val s1= arrayOf("Java", "C++", "Objective-C")
private val s2= arrayOf("男","女")

// 声明下拉列表JComboBox
private val choice1 = JComboBox(s1)  ①
private val choice2 = JComboBox(s2)  ②

init {

    layout =GridLayout(2, 2, 0, 0)
    // 创建标签
    val lblTextFieldLabel = JLabel("选择你喜欢的编程语言:")       

lblTextFieldLabel.horizontalAlignment = SwingConstants.RIGHT
contentPane.add(lblTextFieldLabel)

    // 注册Action事件侦听器,采用Lambda表达式      

choice1.addActionListener { e -> ③
val cb = e.source as JComboBox ④
// 获得选择的项目
val itemString = cb.selectedItem as String ⑤
println(itemString)
}
contentPane.add(choice1)

    // 创建标签
    val lblTextAreaLabel = JLabel("选择性别:")     

lblTextAreaLabel.horizontalAlignment = SwingConstants.RIGHT
contentPane.add(lblTextAreaLabel)

    // 注册项目选择事件侦听器       

choice2.addItemListener { e -> ⑥
// 项目选择
if(e.stateChange == ItemEvent.SELECTED) { ⑦
// 获得选择的项目
val itemString = e.item as String ⑧
println(itemString)
}
}
contentPane.add(choice2)
// 设置窗口大小
setSize(400,150)
// 设置窗口可见
isVisible =true
}

}
上述代码第①行和第②行是创建下拉列表组件对象,其中构造函数参数是字符串数组。下拉列表组件在进行事件处理时,可以注册两种事件监听器:ActionListener和ItemListener,这两个监听器都只有一个抽象函数需要实现,因此可以采用Lambda表达式作为事件处理者,代码第③行和第⑥行分别注册这两个事件监听器。
代码第③行通过e事件参数获得事件源,代码第④行是获得选中的项目。代码第⑦行是判断当前的项目是否被选中,代码第⑧行是从e事件参数中取出项目对象。

24.5.5 列表
Swing中提供了列表(JList)组件,可以单选或多选。
JList常用的构造函数有:
o JList()。创建一个列表对象。
o JList(listData: Array)。创建一个列表对象,listData设置列表中选项。列表中选项内容可以是任意类,而不再局限于String。
下面通过示例介绍列表组件,示例如图24-23所示的界面,界面中有一个列表组件。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第26张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s5/MyFrame.kt
package com.a51work6.section5.s5

import java.awt.BorderLayout
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.ListSelectionModel

class MyFrame(title: String) : JFrame(title) {

private val s1= arrayOf("Java", "C++", "Objective-C")

init {
    // 创建标签
    val lblTextFieldLabel = JLabel("选择你喜欢的编程语言:")       

contentPane.add(lblTextFieldLabel, BorderLayout.NORTH)

    // 列表组件JList
    val list1 =JList(s1)                       ①       

list1.selectionMode = ListSelectionModel.SINGLE_SELECTION ②
// 注册项目选择事件侦听器,采用Lambda表达式。
list1.addListSelectionListener { e -> ③
if(!e.valueIsAdjusting) { ④
// 获得选择的内容
val itemString = list1.selectedValue as String ⑤
println(itemString)
}
}
contentPane.add(list1, BorderLayout.CENTER)

    // 设置窗口大小       

setSize(300, 200)
// 设置窗口可见
isVisible =true
}

}
上述代码第①行创建列表组件对象,代码第②行是设置列表为单选,代码第③行是选择列表事件,代码第④行!e.valueIsAdjusting可判断鼠标释放,e.valueIsAdjusting可以判断鼠标按下。代码第⑤行是取出selectedValue属性获得选中的项目值。

24.5.6 分隔面板
Swing中提供了一种分隔面板(JSplitPane)组件,可以将屏幕分成左右或上下两部分。JSplitPane常用的构造函数有:
o JSplitPane(newOrientation : Int)。创建一个分隔面板,参数newOrientation指定布局方向,newOrientation取值是JSplitPane.HORIZONTAL_SPLIT水平或JSplitPane.VERTICAL_SPLIT垂直。
o JSplitPane(newOrientation: Int, newLeftComponent: Component!,
newRightComponent: Component!)。创建一个分隔面板,参数newOrientation指定布局方向,newLeftComponent左侧面板组件,newRightComponent右侧面板组件。
下面通过示例介绍分隔面板组件,示例如图24-24所示的界面,界面分左右两部分,左边有列表组件,选中列表项目时右边会显示相应的图片。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第27张图片
示例代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s6/MyFrame.kt
package com.a51work6.section5.s6

import java.awt.BorderLayout
import javax.swing.*

class MyFrame(title: String) : JFrame(title) {

private val data = arrayOf("bird1.gif",           

“bird2.gif”, “bird3.gif”, “bird4.gif”,
“bird5.gif”, “bird6.gif”)

init {

    // 右边面板
    val rightPane = JPanel()       

rightPane.layout = BorderLayout(0, 0)
val lblImage = JLabel()
lblImage.horizontalAlignment = SwingConstants.CENTER
rightPane.add(lblImage, BorderLayout.CENTER)

    // 左边面板
    val leftPane = JPanel()       

leftPane.layout = BorderLayout(0, 0)
val lblTextFieldLabel = JLabel(“选择鸟儿:”)
leftPane.add(lblTextFieldLabel, BorderLayout.NORTH)

    // 列表组件JList
    val list1 =JList(data)       

list1.selectionMode = ListSelectionModel.SINGLE_SELECTION
// 注册项目选择事件侦听器,采用Lambda表达式。
list1.addListSelectionListener { e ->
if(!e.valueIsAdjusting) {
// 获得选择的内容
val itemString = list1.selectedValue as String
val petImage ="/images/$itemString" ①
val icon = ImageIcon(MyFrame::class.java.getResource(petImage)) ②
lblImage.icon = icon
}
}
leftPane.add(list1, BorderLayout.CENTER)

    // 分隔面板
    val

splitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPane, rightPane) ③
splitPane.dividerLocation = 100 ④

contentPane.add(splitPane, BorderLayout.CENTER) ⑤

    // 设置窗口大小       

setSize(300, 200)
// 设置窗口可见
isVisible =true
}

}
上述代码分别创建两个面板。然后在代码第③行创建分隔面板,设置布局是水平方向和左右面板。代码第④行splitPane.dividerLocation = 100是设置分隔条的位置。代码第⑤行是将分隔面板添加到内容面板中。
代码第①行是获得图片的相对路径,代码第②行是创建图片ImageIcon对象,MyFrame::class.java.getResource(petImage)语句是获取资源图片的绝对路径。
在这里插入图片描述
24.5.7 使用表格
当有大量数据需要展示时,可以使用二维表格,有时也可以使用表格修改数据。表格是非常重要的组件。Swing提供了表格组件JTable类,但是表格组件比较复杂,它的表现形式与数据分离的,Swing的很多组件都是按照MVC[1]设计模式进行设计的,JTable最有代表性,按照MVC设计理念JTable属于视图,对应的模型是javax.swing.table.TableModel接口实现类,根据自己的业务逻辑和数据实现TableModel接口。TableModel接口要求实现所有抽象函数,使用起来比较麻烦,有时只是使用很简单的表格,此这时可以使用AbstractTableModel抽象类。实际开发时需要继承AbstractTableModel抽象类。
JTable类常用的构造函数有:
o JTable(dm: TableModel!)。通过模型创建表格,dm是模型对象,其中包含了表格要显示的数据。
o JTable(rowData: Array>, columnNames: Array)。通过二维数组和指定列名,创建一个表格对象,rowData是表格中的数据,columnNames是列名。
o JTable(numRows : Int, numColumns:
Int)。指定行和列数创建一个空的表格对象。
如图24-25所示的一个使用JTable表格示例,该表格放置在一个窗口中,由于数据比较多,还有滚动条。下面具体介绍一下如果通过JTable实现该示例。
[1] MVC是一种设计理念,将一个应用分为:模型(Model)、视图(View)和控制器(Controller),它将业务逻辑、数据、界面表示进行分离的函数组织代码,界面表示的变化不会影响到业务逻辑组件,不需要重新编写业务逻辑。
《Kotlin从小白到大牛》第24章:Kotlin与Java Swing图形用户界面编程_第28张图片
这一节先介绍通过二维数组和列名实现表格。这种方式创建表格不需要模型,实现起来比较简单。但是表格只能接受二维数组作为数据。
具体代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section5/s7/MyFrameTable.kt
package com.a51work6.section5.s7

import java.awt.BorderLayout
import java.awt.Font
import java.awt.Toolkit
import javax.swing.JFrame
import javax.swing.JScrollPane
import javax.swing.JTable
import javax.swing.ListSelectionModel
import javax.swing.ListSelectionModel.SINGLE_SELECTION

class MyFrameTable(title: String) : JFrame(title) {

// 获得当前屏幕的宽高
private val 
screenWidth = Toolkit.getDefaultToolkit().screenSize.getWidth()   ①
private val

screenHeight = Toolkit.getDefaultToolkit().screenSize.getHeight() ②

private val table: JTable

// 表格列标题
private var

columnNames = arrayOf(“书籍编号”, “书籍名称”, “作者”,
“出版社”, “出版日期”, “库存数量”)
// 表格数据
private var
rowData = arrayOf(
arrayOf(“0036”, “高等数学”, “李放”,
“人民邮电出版社”, “20000812”, 1),
arrayOf(“0004”, “FLASH精选”, “刘扬”, “中国纺织出版社”, “19990312”, 2),
arrayOf(“0026”, “软件工程”, “牛田”,
“经济科学出版社”, “20000328”, 4),

)

init {

    table =JTable(rowData, columnNames)                   ③
    // 设置表中内容字体
    table.font= Font("微软雅黑",Font.PLAIN, 16)
    // 设置表列标题字体       

table.tableHeader.font = Font(“微软雅黑”, Font.BOLD, 16)
// 设置表行高
table.rowHeight = 40
// 设置为单行选中模式
table.setSelectionMode(SINGLE_SELECTION)
// 返回当前行的状态模型
val rowSM =table.selectionModel
// 注册侦听器,选中行发生更改时触发
rowSM.addListSelectionListener{ e -> ④
//只处理鼠标按下
if(!e.valueIsAdjusting) {
return@addListSelectionListener
}
val lsm= e.source as ListSelectionModel
if(lsm.isSelectionEmpty) {
println(“没有选中行”)
} else
{
val selectedRow = lsm.minSelectionIndex
println(“第” + selectedRow + “行被选中”)
}
} ⑤

    val scrollPane = JScrollPane()                          ⑥       

scrollPane.setViewportView(table) ⑦
contentPane.add(scrollPane, BorderLayout.CENTER)

    // 设置窗口大小       

setSize(960, 640)
// 计算窗口位⑨于屏幕中心的坐标
val x =(screenWidth - 960).toInt() / 2 ⑧
val y =(screenHeight - 640).toInt() / 2 ⑨
// 设置窗口位于屏幕中心
setLocation(x, y)

    // 设置窗口可见
    isVisible =true
}

}
上述代码第①行和第②行是获得当前机器屏幕的高和宽,通过屏幕高和宽可以计算出当前窗口屏幕的居中时的坐标,代码第⑧行和第⑨行是计算这个坐标,由于坐标原点在屏幕的左上角,所以窗口居中坐标公式:
x = (屏幕宽度-窗口宽度) / 2
y = (屏幕高度-窗口高度) / 2
代码第③行是创建JTable表格对象,采用了二维数组和字符串一维数组创建表格对象。代码第④行~第⑤行是,注册事件监听器,监听器当行选择变化时触发。由于ListSelectionListener接口属于SAM接口,所以可以使用Lambda表达式实现该接口。
表格一般都会放到一个滚动面板(JScrollPane)中,这可以保证数据很多超出屏幕时,能够出现滚动条。把表格添加到滚动面板并不是使用add函数,而是使用代码第⑦行的scrollPane.setViewportView(table)语句。滚动面板是非常特殊的面板,它管理这一个视口或窗口,当里面的内容超出视口会出现滚动条,setViewportView函数可以设置一个容器或组件作为滚动面板的视口。

24.6案例:图书库存

在实际项目开发中往往数据是从数据库中查询返回的,数据结构有多种形式,采用自定义模型可以接收任何形式的数据。本节将上一节的图书表格示例采用自定义模型重构一下。
在进行数据库设计时,数据库中每一个表对应Kotlin实体类,实体类是系统的“人”、“事”、“物”等一些名词,例如图书(Book)就是一个实体类了,实体类Book代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section6/Book.kt
package com.a51work6.section6

//图书实体类
data class Book(
// 图书编号
val bookid:String,
// 图书名称
val bookname: String,
// 图书作者
val author:String,
// 出版社
val publisher: String,
// 出版日期
val pubtime: String,
// 库存数量
val inventory: Int
)
实体类可以声明为数据类。
由于目前没有介绍数据库编程,本例表格中的数据是从JSON文件Books.json中读取的,Books.json位于项目的db目录中,JSON文件Books.json的内容如下:
[{“bookid”:“0036”,“bookname”:“高等数学”,“author”:“李放”,“publisher”:“人民邮电出版社”,“pubtime”:“20000812”,“inventory”:1},
{“bookid”:“0004”,“bookname”:“FLASH精选”,“author”:“刘扬”,“publisher”:“中国纺织出版社”,“pubtime”:“19990312”,“inventory”:2},

{“bookid”:“0005”,“bookname”:“java基础”,“author”:“王一”,“publisher”:“电子工业出版社”,“pubtime”:“19990528”,“inventory”:3},
{“bookid”:“0032”,“bookname”:“SOL使用手册”,“author”:“贺民”,“publisher”:“电子工业出版社”,“pubtime”:“19990425”,“inventory”:2}]

从文件Books.json可见整个文档结构是JSON数组,因为JSON字符串的开始和结尾被中括号括起来,这说明是JSON数组。JSON数组的每一个元素是JSON对象,因为JSON对象是用大括号括起来的,代码如下。
{“bookid”:“0032”,“bookname”:“SOL使用手册”,“author”:“贺民”,“publisher”:“电子工业出版社”,“pubtime”:“19990425”,“inventory”:2}
清楚这个JSON文档结构非常必要,当编程时候会根据这个文档结构,解析JSON文档代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section6/SwingDemo.kt
package com.a51work6.section6

import com.beust.klaxon.*

fun main(args: Array) {
MyFrameTable(“图书库存”, readData())
}

// 从文件中读取数据
private fun readData(): List {
// 数据文件
val dbFile ="./db/Books.json"
// 1.JSON解码
val parser:Parser = Parser()
// JSON解码,解码成功返回JSON数组
val jsonArray =parser.parse(dbFile) as JsonArray ①
println(“JSON解码成功完成…”)

// 2.将JSON数组放到List集合中
// 遍历集合
return jsonArray.map {                        ②       

Book(it.int(“bookid”)!!,
it.string(“bookname”)!!,
it.string(“author”)!!,
it.string(“publisher”)!!,
it.string(“pubtime”)!!,
it.int(“inventory”)!!)
} .sortedBy
{ it.bookid } ③
}
上述代码第①行解码JSON文件,返回JSON数组。由于返回的JSON数组的每一个元素是JsonObject,需要转换成为List,即List集合中每一个元素都是Book类型。这种转换的过程可以使用map函数,代码第②行使用map函数进行转换。代码第③行调用sortedBy函数进行排序,it.bookid是指定排序是按照bookid进行。

下面看看模型BookTableModel代码:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section6/BookTableModel.kt
package com.a51work6.section6

import javax.swing.table.AbstractTableModel

class BookTableModel(private val data: List)

AbstractTableModel() { ①

// 列名数组
private val
columnNames = arrayOf(“书籍编号”, “书籍名称”, “作者”,“出版社”, “出版日期”,“库存数量”)

// 获得列数
override fun getColumnCount(): Int = columnNames.size

// 获得行数
override fun getRowCount(): Int = data.size

// 获得某行某列的数据
override fun getValueAt(row: Int, col: Int): Any? { ②

  val (bookid, bookname, author, publisher, pubtime, inventory) = data[row]
  return when (col) {
      0 ->bookid
      1 ->bookname
      2 ->author
      3 ->publisher
      4 ->pubtime
      5 ->inventory
      else-> null
  }

}

// 获得某列的名字
override fun
getColumnName(col: Int): String = columnNames[col] ③
}
上述代码是自定义的模型,它继承了抽象类AbstractTableModel,见代码第①行。抽象类AbstractTableModel要求必须实现getColumnCount()、getRowCount()和getValueAt(row: Int, col: Int)三个抽象函数,getColumnCount()函数提供表格列数,getRowCount()函数提供表格的行数,getValueAt(row: Int, col: Int)函数提供了指定行和列时单元格内容。代码第③行的getColumnName(col: Int)函数不是抽象类要求实现的函数,重写该函数能够给表格提供有意义的列名。

窗口代码如下:
//Kotlin代码文件:chapter24/src/main/kotlin/com/a51work6/section6/MyFrameTable.kt
package com.a51work6.section6

import java.awt.BorderLayout
import java.awt.Font
import java.awt.Toolkit
import javax.swing.JFrame
import javax.swing.JScrollPane
import javax.swing.JTable
import javax.swing.ListSelectionModel

class MyFrameTable(title: String, //图书列表
private val data: List) : JFrame(title) {

// 获得当前屏幕的宽高
private val

screenWidth = Toolkit.getDefaultToolkit().screenSize.getWidth()
private val
screenHeight = Toolkit.getDefaultToolkit().screenSize.getHeight()

private val table: JTable

init {
    val model =BookTableModel(data)
    
    table =JTable(model)
    with(table)

{ ①
// 设置表中内容字体
font =Font(“微软雅黑”,Font.PLAIN, 16)
// 设置表列标题字体
tableHeader.font = Font(“微软雅黑”, Font.BOLD, 16)
// 设置表行高
rowHeight = 40
// 设置为单行选中模式
setSelectionMode(SINGLE_SELECTION)
}
// 返回当前行的状态模型
val rowSM =table.selectionModel
// 注册侦听器,选中行发生更改时触发
rowSM.addListSelectionListener { e ->
//只处理鼠标按下
if(!e.valueIsAdjusting) {
return@addListSelectionListener
}
val lsm= e.source as ListSelectionModel
if(lsm.isSelectionEmpty) {
println(“没有选中行”)
} else
{
val selectedRow = lsm.minSelectionIndex
println(“第” + selectedRow + “行被选中”)
}
}

    val scrollPane = JScrollPane()       

scrollPane.setViewportView(table)
contentPane.add(scrollPane, BorderLayout.CENTER)

    // 设置窗口大小       

setSize(960, 640)
// 计算窗口位于屏幕中心的坐标
val x =(screenWidth - 960).toInt() / 2
val y =(screenHeight - 640).toInt() / 2
// 设置窗口位于屏幕中心
setLocation(x, y)
// 设置窗口可见
isVisible =true
}

}
窗口代码与24.5.7节的类似,需要注意代码第①行使用了with函数,with函数能够对象的多个函数。其中代码不再赘述。

本章小结

本章介绍了Kotlin中借助于Java Swing技术编写图形用户界面应用。详细介绍了Swing的布局管理、Swing常用组件,最后介绍了一个JTable案例。

你可能感兴趣的:(Kotlin从小白到大牛)