[笔记] Kotlin式的Swing和AWT
JVM生态的GUI历史
参考: https://www.zhihu.com/question/323058868/answer/811401387
- AWT是调用操作系统API绘制控件,SWING是自绘,他们都在Java标准库里
- Swing的API与AWT基本兼容
- 一些比较新的JVM GUI不在Java标准库里,如JavaFX(系统API),SWT(系统API)等
AWT比较老,有一个中文乱码问题
Java与传统OOP式代码参考
Java语法十分繁琐,个人也不想学,仅列出一个网上的代码作为参考
import java.awt.*;
public class Example {
Example()
{
Frame fr=new Frame();
Label lb = new Label("This is a label");
fr.add(lb);
TextField t = new TextField();
fr.add(t);
fr.setSize(400, 200);
fr.setLayout(new FlowLayout());
fr.setVisible(true);
}
public static void main(String args[])
{
Example ex = new Example();
}
}
Kotlin风格的Swing和AWT
我们用Kotlin的T.apply, T.let, T.also等作用域函数来将传统的Java继承式代码重写,避开那些繁琐的类,继承,构造函数,专注于UI结构,达到同样的效果。
比如一个UI的架构是这样的
Frame
Panel
Button
Button
Label
我们希望代码也差不多是这个结构:
Frame() {
Panel() {
Button()
Button()
Label()
}
}
而不是
Frame fr=new Frame();
fr.setLayout(new FlowLayout());
Panel panel = new Panel();
fr.add(panel);
Button b1 = new Button();
b1.setLabel("OK");
panel.add(b1);
Button b2 = new Button();
b1.setLabel("OK2");
panel.add(b2);
TextField t = new TextField();
panel.add(t);
使用Kotlin的apply,also,let等函数来简化
以这个典型的new-set-add式的代码为例,Kotlin可以用apply将set于new合并,并衔接一个also来完成add。
Panel panel = new Panel();
Button b1 = new Button();
b1.setLabel("OK");
panel.add(b1);
Panel().apply {
Button().apply {
label = "OK"
}.also(::add)
}
当组件的属性较为复杂时,代码就能保持清晰
JDialog(mainFrame, "Title").apply {
//设置Dialog属性
layout = GridLayout(3,1)
size = Dimension(80,180)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
//设置子组件
JLabel("这是消息").also(::add)
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
dispose()
}
}.also(::add)
JButton("取消").apply{
size = Dimension(30,15)
addActionListener {
dispose()
}
}.also(::add)
//设置Dialog属性
isVisible = true
}
当我们需要上层组件的名字的时候怎么办?可以借助嵌套函数中内层能访问外层变量的特性,只需给外层变量取个名字即可,若要修改,则在外层包装一个函数。
JFrame().apply {
//设置Dialog属性
title = "Title"
layout = GridLayout(3,1)
size = Dimension(80,180)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
//设置子组件
val label1 = JLabel("这是消息").also(::add)
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
label1.text = "OK"
}
}.also(::add)
//设置Dialog属性
isVisible = true
}
点击确认,label1的文字就变成了OK
局限性
这种方法有局限性,比如一个很大的问题就是不能访问同一层后面的组件:
JFrame().apply {
//设置Dialog属性
title = "Title"
layout = GridLayout(3,1)
size = Dimension(80,180)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
//设置子组件
val label1 = JLabel("这是消息").also(::add)
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
button2.text = "OK" // Unresolved reference: button2
}
}.also(::add)
val button2 = JButton("取消").apply{
size = Dimension(30,15)
addActionListener {
}
}.also(::add)
//设置Dialog属性
isVisible = true
}
button2的定义在button1之后,导致button1无法访问button2,我们不得不将button2的声明提到前面来,写出一个如Java般繁琐的代码
JFrame().apply {
//设置Dialog属性
title = "Title"
layout = GridLayout(3,1)
size = Dimension(80,180)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
val button2 = JButton("取消") //提前声明
//设置子组件
val label1 = JLabel("这是消息").also(::add)
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
button2.text = "OK"
}
}.also(::add)
JButton("取消").apply{
size = Dimension(30,15)
addActionListener {
}
}.also(::add)
//设置Dialog属性
isVisible = true
}
即使用上Kotlin那些特性,也很难写出很好看的代码,甚至不如Java版本清晰
var setButton2Text :((text :String)->Unit)? = null
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
setButton2Text!!("Good")
}
}.also(::add)
JButton("取消").apply{
size = Dimension(30,15)
}.also(::add).let { setButton2Text = { text -> it.text = text } }
Kotlin式AWT示例
import java.awt.*
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import kotlin.system.exitProcess
fun main(args: Array) {
Frame().apply {
val mainFrame = this
size = Dimension(800,600)
title = "AWT example"
layout = FlowLayout()
addWindowListener(object :WindowAdapter() {
override fun windowClosing(windowEvent :WindowEvent) {
exitProcess(0)
}
})
Panel().apply {
layout = FlowLayout()
background = Color.CYAN
val text1 = TextField().apply {
text = "Hello!"
}.also(::add)
Button().apply {
label = "Hey"
addActionListener {
Dialog(mainFrame, "Title").apply {
layout = GridLayout(2,1)
size = Dimension(80,180)
Label("Info").apply {
alignment = 1
}.also(::add)
Button("OK").apply{
bounds = Rectangle(30,15)
addActionListener {
dispose()
}
}.also(::add)
isVisible = true
}
}
}.also(::add)
Button().apply {
label = "Click me"
addActionListener {
label = "clicked!"
text1.text = "clicked!"
}
}.also(::add)
}.also(::add)
}.isVisible = true
}
Kotlin式Swing示例
import java.awt.*
import javax.swing.*
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import kotlin.system.exitProcess
fun main(args: Array) {
JFrame().apply {
val mainFrame = this
size = Dimension(800,600)
title = "First AWT example"
layout = FlowLayout() //提供三种FlowLayout,BorderLayout,GridLayout
addWindowListener(object :WindowAdapter() {
override fun windowClosing(windowEvent :WindowEvent) {
exitProcess(0)
}
})
JPanel().apply {
layout = FlowLayout()
background = Color.CYAN
val text1 = JTextField().apply {
text = "Hello!"
}.also(::add)
JButton().apply {
var result :Result = Result.failure(Exception())
text = "Test"
addActionListener {
JDialog(mainFrame, "Title").apply {
layout = GridLayout(3,1)
size = Dimension(80,180)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
JLabel("这是消息").also(::add)
JButton("确认").apply{
size = Dimension(30,15)
addActionListener {
text1.text = "OK"
dispose()
}
}.also(::add)
JButton("取消").apply{
size = Dimension(30,15)
addActionListener {
text1.text = "Not OK"
dispose()
}
}.also(::add)
isVisible = true
}
}
}.also(::add)
JButton().apply {
text = "Click me"
addActionListener {
text = "clicked!"
text1.text = "clicked!"
}
}.also(::add)
}.also(::add)
}.isVisible = true
}