Categories
Kotlin Model-View-Controller TornadoFX User Interface

Introduction to TornadoFX

Background

Dynamic simulations are most useful when they are attached to some form of a graphical user interface (GUI). A few years ago, when working on Apps for the MacOS and iOS platforms, I used Cocoa and Cocoa touch to build interfaces with Xcode. The Mac designs follow the MVC (Model-View-Controller) pattern closely. I used to construct the views in Storyboards and control these views with ViewControllers. The modern way of creating interfaces for the Mac ecosystem is with SwiftUI, which I have not yet tried.

Going further back in time (couple of decades), when writing PC apps with Java, the built-in graphics system Swing was my choice for GUI’s. Swing was, and still is, part of the Java development package (SDK).

Since then, a more contemporary graphical system was developed called JavaFX. It was aimed at replacing Swing but this never happened the way it was intended. Today Swing is still part of the Java development package and JavaFX is a separate open-source package https://openjfx.io.

JavaFX

According to the JavaFX website “JavaFX is an open source, next generation client application platform for desktop, mobile and embedded systems built on Java.”

Why am I bringing up JavaFX when I’m currently not doing much Java programming? The reason is Kotlin and TornadoFX. As I mentioned in a previous blog post, Kotlin is a separate language that targets the JVM. TornadoFX is also a separate GUI language. However, it is written in Kotlin and uses JavaFX as the base for all its graphical constructs. In other words, Java and JavaFX go together the same way as Kotlin and TornadoFX go together. But TornadoFX is actually JavaFX wrapped in some Kotlin constructs. Confusing? Let me try to explain by way of a simple example inspired by the book

Learn JavaFX 17: Building User Experience and Interfaces with Java 2nd ed. Edition by  Kishori Sharan and Peter Späth.

Here is the output of the simple JavaFX app. It has two labels, one textfield and two buttons. The user enters a name in the textfield and clicks the “Say Hello” button to get a greeting. When done you click the “Exit” button.

Output from a simple JavaFX application.

While being a very simple App it illustrates several important aspects of any GUI:

  • How to invoke the App and make a window visible.
  • How to enter text that can be consumed by the App.
  • How to create buttons that respond when clicked.
  • How to display output created by the App.

The code below shows how the App is constructed using Java and JavaFX.

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SimpleFXApp extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage stage) {
        Label nameLabel = new Label("Enter a name:");
        TextField nameField = new TextField();

        Label msg = new Label();
        msg.setStyle("-fx-text-fill: blue;");

        // Create buttons
        Button sayHelloButton = new Button("Say Hello");
        Button exitButton = new Button("Exit");

        // Add the event handler for the Say Hello button
        sayHelloButton.setOnAction(e -> {
            String name = nameField.getText();
            if (name.trim().length() > 0) {
                msg.setText("Hello " + name);
            } else {
                msg.setText("Hello to you");
            }
        });

        // Add the event handler for the Exit button
        exitButton.setOnAction(e -> Platform.exit());

        // Create the root node
        VBox root = new VBox();

        // Set the vertical spacing to 5px
        root.setSpacing(5);

        // Add children nodes to the root node
        root.getChildren().addAll(nameLabel, nameField, msg, sayHelloButton, exitButton);

        Scene scene = new Scene(root, 350, 150);
        stage.setScene(scene);
        stage.setTitle("Simple JavaFX Application");
        stage.show();
    }
}

TornadoFX

Now let’s explore how we can write the same simple App using Kotlin and TornadoFX https://tornadofx.io.

Since TornadoFX lends itself nicely to MVC, I chose this file structure for the example. Thus, the first code snippet is just the View. It practically reads like prose in that labels, a textfield and two buttons are introduced by the builder functions. More on those later.

package view

import app.Styles
import controller.ViewController
import tornadofx.*
import kotlin.system.exitProcess

class MainView : View("Simple TornadoFX Application") {

    private val viewController: ViewController by inject()

    override val root = vbox {
        spacing = 5.0

        label("Enter a name:")
        textfield(viewController.name)
        label(viewController.labelText) {
            addClass(Styles.heading)
        }
        button("Say Hello") {
            action {
                viewController.makeGreeting()
            }
        }
        button("Exit") {
            action {
                exitProcess(0)
            }
        }
    }
}

Next we introduce the view controller code. It holds the values of the output (labelText) and the input textfield (name). It also contains the function that makes the greeting when the “Hello button” is clicked.

Notice that the MainView is derived from a View and that the ViewController is derived from a Controller. Both View and Controller are Singletons meaning that only one object of each class is constructed in the application. The view knows about the controller by “inject()”. The controller is instantiated the first time it is used in the view. The view is instantiated by the App as shown below.

package controller

import javafx.beans.property.SimpleStringProperty
import tornadofx.*

class ViewController: Controller() {

    val labelText = SimpleStringProperty()
    val name = SimpleStringProperty()

    fun makeGreeting() {
        val enteredName = name.value
        if (enteredName.trim().isNotEmpty()) {
            labelText.value = "Hello $enteredName"
        } else {
            labelText.value = "Hello to you"
        }
    }
}

We need two other pieces for the TornadoFX App to function properly. The first is the stage (window) for the application by settings its size and making it display. The MainView singleton is instantiated at the start of the App.

package app

import javafx.stage.Stage
import tornadofx.*
import view.MainView

class MyApp: App(MainView::class, Styles::class) {
    override fun start(stage: Stage) {
        with(stage) {
            width = 350.0
            height = 200.0
        }
        super.start(stage)
    }
}

The second piece is a Styles class that controls the font size and color.

package app
import javafx.scene.text.FontWeight
import tornadofx.*

class Styles : Stylesheet() {
    companion object {
        val heading by cssclass()
    }

    init {
        label and heading {
            padding = box(2.px)
            fontSize = 20.px
            fontWeight = FontWeight.BOLD
            textFill = c("blue")
        }
    }
}

By running the Kotlin/TornadoFX code we get the same output as in the Java example.This should not be surprising given that JavaFX is the engine that drives TornadoFX. What should be surprising (at least it was to me) is how the TornadoFX code can look so simple and not explicitly mention objects and classes of types TextField, Label, Button, etc.? I will try to explain below.

Output from a simple TornadoFX application.

Kotlin Magic

TornadoFX manages to invoke all the same JavaFX code that Java uses but in a much more streamlined fashion. This is possible by use of Kotlin’s extension functions and trailing lambdas.

Let’s compare the implementation of the “Hello button” both in Java and Kotlin. In Java we have the following code snippet:

        Button sayHelloButton = new Button("Say Hello");
        
        sayHelloButton.setOnAction(e -> {
            String name = nameField.getText();
            if (name.trim().length() > 0) {
                msg.setText("Hello " + name);
            } else {
                msg.setText("Hello to you");
            }
        });

In TornadoFX we don’t mention the class Button. Instead we call a function with the name “button” (lowercase “b”):

        button("Say Hello") {
            action {
                viewController.makeGreeting()
            }
        }
    fun makeGreeting() {
        val enteredName = name.value
        if (enteredName.trim().isNotEmpty()) {
            labelText.value = "Hello $enteredName"
        } else {
            labelText.value = "Hello to you"
        }
    }

The question is, how does the TornadoFX code generate the same code as JavaFX? The answer lies in the use of Kotlin extension functions and lambdas to construct builder functions. For example, look at the extension function “button” below taken from the TornadoFX’s controls library https://github.com/edvin/tornadofx/blob/master/src/main/java/tornadofx/Controls.kt#L309-L314. It takes three arguments, a String, a graphic node and a lambda function. The function is set equal to a JavaFX Button which in turn attaches the lambda function to itself.

In the example above, the lambda function is the function “action”, defined in the code below. It is also an extension function and it takes only one argument, a lambda function.

fun EventTarget.button(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) = Button(text).attachTo(this, op) {
    if (graphic != null) it.graphic = graphic
}

fun ButtonBase.action(op: () -> Unit) = setOnAction { op() }

If you combine the result of the builder function, button(…), and its member function, action(…), you get the effect of a JavaFX Button handling an action event as illustrated below.

        button("Say Hello") {
            action {
                viewController.makeGreeting()
            }
        }

        ===

        Button("Say Hello").onAction = EventHandler { e: ActionEvent? -> viewController.makeGreeting() }

Discussion

TornadoFX offers a set of Kotlin constructs operating on JavaFX that makes it possible to build GUI’s simply and cleanly. I have not done any complex interfaces with TornadoFX yet but I plan on using it going forward.

Categories
Dynamic Modeling Kotlin Modeling Languages

Kotlin and Gradle

Introduction

As I mentioned in my previous post, my new favorite programming language is Kotlin. I particularly like it because it has much of Python’s flexibility but the speed of Java, an awesome combination. I’ve done a few execution speed comparisons and find that for dynamic simulations of my chemical engineering models, Kotlin is 10-15 times faster than Python.

Now, execution speed is not everything. Python’s ease of use and rich library of readily available packages account for a great deal of this language’s popularity. I described in a previous blog post how one can create virtual environments and use pip to safely download packages and manage their dependencies. This is a big deal and a huge advantage for Python. In particular the numerical package numpy and the plotting package matplotlib are hard to beat.

So the purpose of today’s post is to ask what are the Kotlin equivalents to virtual environments, pip, numpy and matplotlib? The short answer is Gradle and Lets-Plot. I elaborate below.

Gradle

Gradle is an open-source build automation tool (https://gradle.org). You can download Gradle separately and use it to build almost any software project. Personally I use the plug-in provided in JetBrains IntelliJ IDEA tool. Since it took me a bit of research and tinkering before I figured out how to use Gradle, I decided to share my findings here and perhaps help others who are new to Kotlin and Gradle.

The first step in the process is to create a new Gradle project from IntelliJ. This looks as follows:

The New Project window in IntelliJ IDEA. Make note of all the selected and checked fields

The next step is to give the project a name. I call this ColumnSimulation since I will be constructing a dynamic simulation of a distillation column with PID controllers.

After clicking “Finish” I have a ready-made Gradle project that I can start populating with source code. But before I do that I’d like to link this new project to another Gradle project where I have most of my reusable process and instrument models. This other project I’ve named SyMods. This is how I link the two together.

First click on the little icon at the bottom left corner of the IntelliJ IDE. From the pop-up, select Gradle as in the picture below.

Pop-up window from clicking the lower left icon on IntelliJ IDEA.

This opens the Gradle window on the right hand side of the IDE. The ‘+’ sign at the top of the Gradle window allows you to add another Gradle project to be linked with the first. By selecting the “build.gradle.kts” within the SyMods project, I can now add this project as a companion project to my newly created ColumnSimulation project.

Find the second Gradle project in the file system and click on the build.gradle.kts file to include it.

Both projects then show up in the Gradle window. By right-clicking on the ColumnSimulation project I can tell the build system that I want a “Composite Build Configuration”. Both projects are then built together.

Gradle window showing two projects. Right-click the first and select Composite Build

I close the Gradle window and focus on the Project window on the left side of the IDE. It looks like the picture below where I have the “build.gradle.kts” file open for the ColumnSimulation project. I now add some dependencies particularly for plotting.

Gradle window closed and focus is on the two projects.

Lets-Plot and Other Dependencies

One of the beauties of Gradle is that it manages external libraries and dependencies for you. All you have to do is find the URL to the particular library you want to include and add this to the dependency section of the build.gradle.kts file. Below I show how I have added the appropriate links for JetBrains’ Lets-plot library. I also added a library for working with csv files and finally told the ColumnSimulation project that I will be importing models from my own project SyMods (that I previously linked and will be built along with my ColumnSimulation project).

Dependencies added to the gradle.build file. Notice the little Gradle icon in the upper right corner of this picture. To update the build configuration, this icon should be clicked after changes have been made to the build file.

We are now ready to write some application code. Notice that by having my “library” project, SyMods, available as a linked project I can make changes in the library as if I were working on it separately. This is very useful since I often discover some features in the application that could be reused and thus belong in the library.

Summary

Kotlin has proven to be an effective language in writing dynamic simulation models. To manage projects with Kotlin and to provide external libraries and dependencies, Gradle is the preferred tool. I have shown how to make a Gradle project, link it to other projects and include external dependencies for reading csv-files and plotting.