JVM桌面框架的状态:TornadoFX


JavaFX JavaFX开始时是一种名为JavaFX script的脚本语言。Sun Microsystems打算在较小程度上与Adobe Flex(现在称为Apache Flex)和Microsoft Silverlight竞争。

2010年,在同时收购Sun的Oracle的Java One上,甲骨文宣布它将在保留API的同时停止该语言的开发。随着2014年发布的Java 8的发布,JavaFX成为了Swing API的正式后继产品:Swing API从那时起才得到错误修复。

过去,JavaFX一直包含在Oracle JDK中,直到版本11。

但是它一直是一个单独的项目,也可以与JDK分开安装。

—从使用Gluon工具启动JavaFX项目开始 与Swing相比,JavaFX添加了应用程序抽象。以下是JavaFX API的概述:

20210220115515.png

此外,您可以采用两种不同的方法来创建JavaFX用户界面:

  1. 用纯Java代码定义所有对象
  2. 或使用与Java代码集成的基于XML的布局文件(FXML)

这是针对同一应用程序的前者和后者的示例。

Tornado FX

Kotlin允许改进Java API,以提供更好的开发人员体验。我们可以自己做。但是Tornado FX项目已经解决了这个问题。

这是API的鸟瞰图:

20210220115854.png

与普通的JavaFX相比,Tornado FX有很多好处。这里是其中的一些。

组件和布局DSL

与Groovy一样,Kotlin允许创建可用的DSL。与Groovy不同,默认情况下,创建的DSL是类型安全的。您可以找到我之前关于DSL的实验中的两个。

  • Kaadin,用于通过Vaadin创建用户界面的DSL
  • DSL的雏形,用于配置Hazelcast

同样,Tornado FX通过DSL提供JavaFX的所有现成组件和布局。这是一个看起来像的例子:

vbox {
  text("Name")
  textfield()
  button("Button").setOnAction {
  println("Button pressed")
  }
}

一些布局允许更复杂的配置。例如,JavaFX提供了GridPane类似于AWT的布局GridbagLayout。您需要将配置作为GridPaneContraints每个布局元素的对象传递。这是一个示例:

override val root = gridpane {
  padding = insets(space)
  textfield {
    gridpaneConstraints {
      columnIndex = 0
      fillWidth = true
      hGrow = Priority.ALWAYS
      marginBottom = space
    }
  }
  button("Button") {
    gridpaneConstraints {
      columnIndex = 1
      hAlignment = HPos.RIGHT
      marginBottom = space
    }
  }
  textfield {
    gridpaneConstraints {
      rowIndex = 1
      fillWidth = true
      hGrow = Priority.ALWAYS
    }
  }
  textfield {
    gridpaneConstraints {
      columnRowIndex(1, 1)
      fillWidth = true
      hGrow = Priority.ALWAYS
      marginLeft = space
    }
  }
}

尽管看起来不那么可读,但是IDE可以提供巨大的帮助。使用IntelliJ IDEA,您可以折叠不重要的位:

block-fold-idea.jpg

我倾向于在可能的情况下为每个组件创建专用的类,而不是使用在实例化时配置的泛型类。它不适用于现有DSL,因为我需要用自己的DSL进行补充。

控制器

Tornado FX的控制器以MVC模式实现C部分。他们负责封装业务逻辑。UI线程永远不要运行长时间运行的任务,因为它将使它无响应。由于控制器可以执行此类任务,因此您应根据具体情况进行决策。最后,您可以将一个控制器(见下文)作为单例注入到其他组件中。

该API不强制执行任何要求。由开发人员根据上述准则设计控制器。 例如,这是一个控制器,当它收到另一种事件时将触发一个事件:

class PathModelController : Controller() {
  init {
    subscribe<DirectoryPathUpdatedEvent> {
      fire(PathModelUpdatedEvent(it.path))
    }
  }
}

Tornado FX的控制器可以注入视图中。这种方法将视图与逻辑结合在一起。我宁愿换一种方式:将视图注入控制器。因此,有可能以不同的逻辑重用UI组件。现有设计允许在不同的UI组件内重用相同的逻辑,这种逻辑的使用频率要低得多。

依赖注入

Tornado FX提供依赖注入。该API提供了两种方式来注入依赖项:

  1. 使用inject()委托:
class MyView: View("My View") {
  val myController: MyController by inject()
  val myController2 by inject<MyController>()
}
  1. 明确调用该find()函数:
class MyView: View("My View") {
  val myController = find(MyController::class)
  val myController2 = find<MyController>()
}

请注意,inject()在使用ViewController,但你需要使用find()其他类。

Event Bus

TornadoFX提供了一个单例作用域的事件总线。它的用法不过是古典。

事件类必须从FXEvent超类继承。TornadoFX需要设置用于管理事件的线程,无论是应用线程还是后台线程。长时间运行的任务应在后台线程上运行。

Component提供fire()将事件推送到总线的功能。它还提供了register()通知事件接收的功能,按类型过滤。

看起来像这样:

class FooEvent: FXEvent(BackgroundThread)
class BarEvent: FXEvent(BackgroundThread)

class Dummy {
  init {
    subscribe<FooEvent> {
      println(it)           
    }
  }
  fun bar() {
    fire(BarEvent())         
  }
}
  1. 当事件总线收到 FooEvent
  2. 寄一个 BarEvent

结论

在开发了一个简单的演示应用程序之后,我对JavaFX和Tornado FX都没有意见。需要更多经验。我喜欢嵌入式事件总线,但是不喜欢控制器和UI组件之间的关系设计。

在所有情况下,如序言中所述,Swing不会获得任何更新。无论您是否喜欢,JavaFX都是可用选项的一部分。


原文链接:http://codingdict.com