[笔记] Kotlin式的Swing和AWT


JVM生态的GUI历史

参考: https://www.zhihu.com/question/323058868/answer/811401387

  1. AWT是调用操作系统API绘制控件,SWING是自绘,他们都在Java标准库里
  2. Swing的API与AWT基本兼容
  3. 一些比较新的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
}