Kotlin编写JavaFX的顺滑

项目代码库

项目代码

前言

谁会读这篇文章

  1. 致力于成为Kotlin桌面程序员的新手
  2. Kotlin爱好者想开发点桌面GUI来炫耀或者练习
  3. JavaFX爱好者

写作目的

  1. 把自己最近学习的心得记录下来,置于外部存储器
  2. 对初学者有一点点帮助
  3. 能够启发社区来投入JavaFX桌面开发

Kotlin桌面开发

Kotlin主要是为解决Android而开发的JVM编程语言,但是Kotlin的生命力使其迅速突破移动端。也有人说Kotlin四处出击,又是JVM又是Javascript,又是Native,没一个足够成熟,除提供语法糖之外并没有什么意义。但是我们作为用户,不需要去考虑那么多复杂的玩意。简单地说,试一试,好用就用。毕竟Talk is cheap, just show the code.

JavaFx

JavaFX 官方网站:JavaFX

“JavaFX是一个开源的下一代客户端应用平台,用于桌面、移动和嵌入式系统,建立在Java之上。它是由许多个人和公司合作完成的,目标是为开发丰富的客户端应用程序制作一个现代、高效和功能齐全的工具包。”

Kotlin开发工具

目前用于开发Java的IDE普遍提供插件进行Kotlin开发。当然,最为方便的应该试试Jetbrains公司的Intelli IDEA,因为Kotlin就是他们公司的崽。

免费的社区版也就够用。

Kotlin最顺滑的地方就是Kotlin调用Java库非常简单,无缝调用加上语法糖,简直比Java自己调用还简单。

Kotlin+JavaFX的形状

HelloFXKt

还是拿26行的JavaFX程序,HelloFX.java,作为开始。

package hellofx

import javafx.application.Application
import javafx.scene.Scene
import javafx.scene.control.Label
import javafx.scene.layout.StackPane
import javafx.stage.Stage

class HelloFX : Application() {
    override fun start(stage: Stage) {
        val javaVersion = System.getProperty("java.version")
        val javafxVersion = System.getProperty("javafx.version")
        val l = Label("Hello, JavaFX $javafxVersion, running on Java $javaVersion.")
        val scene = Scene(StackPane(l), 640.0, 480.0)
        stage.scene = scene
        stage.show()
    }
}

fun main(args: Array<String>) {
    Application.launch(HelloFX::class.java, *args)
}

程序大同小异,并没啥不同的。main函数从类里面拿出来,launch的调用也有一点点改变。去掉publicnewextends,增加overrideset方法变成属性赋值。还有字符串模板,写+${}也没有很大区别。能看懂Java的基本上也能看懂Kotlin。

而且,上面的代码是用Jetbrains Intelli IDEA自动转化Java代码得到的,改动非常少(只有main方法的部分)。

MVVM

但是有Kotlin之后,我们可以用干净利索点的方式来写。

微软帮助

MVVM模式有三个核心组件:模型、视图和视图模型。每一个都有不同的作用。下图显示了这三个组件之间的关系。微软官方MVVM解释
除了了解每个组件的职责外,了解它们如何相互作用也很重要。在高层次上,视图 "知道 "视图模型,而视图模型 "知道 "模型,但模型不知道视图模型,而视图模型也不知道视图。因此,视图模型将视图与模型隔离开来,并允许模型独立于视图来发展。

使用MVVM模式的好处有以下几点:

  • 如果有一个现有的模型实现封装了现有的业务逻辑,那么改变它可能会很困难或有风险。在这种情况下,视图模型作为模型类的适配器,使你能够避免对模型代码做出任何重大的改变。
  • 开发人员可以为视图模型和模型创建单元测试,而不使用视图。视图模型的单元测试可以行使与视图所使用的完全相同的功能。
  • 应用程序的UI可以不修改业务代码的情况下被重新设计。
  • 在开发过程中,设计师和开发人员可以独立地、同时地在他们的组件上工作。设计师可以专注于视图,而开发人员可以在视图模型和模型组件上工作。

有效使用MVVM的关键在于理解如何将应用代码纳入正确的类中,以及理解这些类是如何互动的。

JavaFX例子

我们把Hellofx程序做一点点小改动,增加一个按钮。
在这里插入图片描述
点击这个按钮,原来的文本标签变成显示JavaFX和Java的版本。
在这里插入图片描述

程序设计

JavaFX的主程序为Application类不变。这个类显示View作为GUI用户界面,数据存储在Model中,ViewModel作为数据和界面解耦的部分存在。在这里插入图片描述

主程序

主程序维持JavaFX应用的通用结构。继承Application,实现start方法,在这个方法中创建Model对象,ViewModel对象,Scene对象,显示在屏幕上。

import javafx.application.Application
import javafx.scene.Scene
import javafx.stage.Stage

class HelloFX : Application() {
    override fun start(stage: Stage) {

        val m = Model()
        val vm = ViewModel(m)

        val root = getRoot(vm.versionText, vm::updateVersionText)
        val scene = Scene(root, 640.0, 480.0)
        stage.scene = scene

        stage.show()
    }
}


fun main(args: Array<String>) {
    Application.launch(HelloFX::class.java, *args)
}

程序中,界面的元素为Scene对象构造函数中的root,从这里看到,root为一个函数的范围值,这个函数的参数包括一个文本属性,一个函数。

下面就是界面设计,实际上也就是View多对应的那个函数的实现。

界面设计

界面包括一个Label和一个Button。View中处理界面的组合方式,处理界面的对齐方式,处理一切与数据不相关的部分。

fun textLabel(t: SimpleStringProperty): Node{
    return Label(t.value).apply {
        textProperty().bind(t)
    }
}
fun updateButton(clickAction: () -> Unit): Node = Button("Check").apply {
    setOnAction {
        clickAction()
    }
}

fun getRoot(text: SimpleStringProperty, clickAction: () -> Unit): Parent = VBox(10.0).apply {
    children.add(textLabel(text))
    children.add(updateButton(clickAction))
    alignment = Pos.CENTER
}

可以看到,getRoot调用两个函数来产生文本标签和按钮,这两个参数,一个绑定(下次把这个翻译出来)到标签的文本属性,一个函数绑定到按钮的事件。

这样的方式,在JetPack Compose中称为声明式界面设计。完全没有假设别的东西。View部分只对一个文本属性SimpleStringProperty和一个() -> Unit的函数提出要求。这就是MVVM中核心的要素,分割责任。这个部分可以任意更改,任意变更,只需要满足设计条件:

  • 界面跟踪一个文本的变化;
  • 界面出发一个函数调用。

这种方式保证了充分的内聚性。

Model设计

这里的数据模型非常简单。考虑到目前的交互是单向的,Model只需要提供数据,并没有反馈数据。

data class Model(
    val javaVersion: String = System.getProperty("java.version"),
    val javafxVersion: String = System.getProperty("javafx.version")
)

ViewModel

ViewModel沟通Model和Application,因此其构造函数包含Model的实例作为参数。

import javafx.beans.property.SimpleStringProperty

class ViewModel(private val m: Model) {
    val versionText = SimpleStringProperty("Hello!")

    fun updateVersionText() {
        versionText.value = "Hello, JavaFX ${m.javafxVersion}, running on Java ${m.javaVersion}."
    }
}

结论

通过一个特别简单的例子,可以看到JavaFX+Kotlin之后,界面的开发是多么的顺滑。

上面的程序有几个特点:

  • 界面不需要知道任何ViewModel或者Model的细节,也不需要维护相应的对象引用;
  • 界面用函数调用的形式来实现,通过绑定来明确其交互的效果,语义和语法都非常清晰;
  • 界面的设计可以单独完成,并且自由更改;
  • Model的部分不需要考虑任何可视化和人机交互,只需要单独考虑业务逻辑;
  • Model可以单独开发,自由更改;
  • 交互的业务逻辑在ViewModel中实现;
  • ViewModel持有Model对象的引用;
  • 当Model的接口改变时,需要修改ViewModel的代码,因此Model和ViewModel是紧耦合;
  • View和其他部分是松耦合,只与Application耦合,不依赖于程序的任何其他部分;
  • Model和其他部分是松耦合,不依赖于程序中的任何部分。
  • 最终的界面交互逻辑和业务交互逻辑的关联由Application来维护。
posted @ 2022-02-13 12:31  大福是小强  阅读(95)  评论(0编辑  收藏  举报  来源