Download presentation
Presentation is loading. Please wait.
1
Super charged JavaFX with Kotlin and TornadoFX
2
Introduction Developer at SYSE – Norwegian hosting company
Project lead for TornadoFX and FXLauncher My day job is Java/Kotlin programming
3
Why TornadoFX? JavaFX is a UI toolkit, not a framework
JavaFX is powerful, but verbose Kotlin presents some unique opportunities for APIs DIY: Every developer creates his own tools to reduce boiler plate and perform common tasks.
4
Statically typed JVM language from JetBrains
More concise syntax, less noise from types/declarations Specific DSL language constructs Higher order functions, Lambda with receiver, Partial support for reified generics
5
Goals Reduce boilerplate, but never hide JavaFX APIs
Add commonly needed components Wizard, Forms, DataGrid, ViewModel, Validation, ListMenu, Drawer, Workspace ++ Improve existing components Data Driven TreeView, TableView Row Expansion, Auto Complete ComboBox Beautiful, understandable, refactorable code! If the code is expressing your intent, there is no room for bugs \
6
Key Features Type Safe Builders Type Safe CSS
Workspace Business Framework (RCP) MVC / MVP / MVVM and similar UI patterns Dependency Injection EventBus Live reloading of Views and CSS REST Client and JSON Alternative to FXML, programmatically create UI
7
Code comparison JavaFX: Create a TableView with two columns TableView<Customer> table = new TableView<>(customers); TableColumn<Customer, String> firstNameColumn = new TableColumn<>("First Name"); firstNameColumn.setCellValueFactory(param -> param.getValue().firstNameProperty()); TableColumn<Customer, String> lastNameColumn = new TableColumn<>("Last Name"); lastNameColumn.setCellValueFactory(param -> param.getValue().lastNameProperty()); table.getColumns().addAll(firstNameColumn, lastNameColumn); This code does exactly the same. Nothing is left out, no parts of the JavaFX API are hidden.
8
Code comparison TornadoFX: Create a TableView with two columns
tableview(customers) { column("First Name", Customer::firstNameProperty) column("Last Name", Customer::lastNameProperty) }
9
Code comparison JavaFX: Custom TableCell
TableColumn<Customer, Number> ageColumn = new TableColumn<>("Age"); ageColumn.setCellValueFactory(param -> param.getValue().ageProperty()); ageColumn.setCellFactory(col -> new TableCell<Customer, Number>() { public void updateItem(Number age, boolean empty) { super.updateItem(age, empty); setText(null); setGraphic(null); if (!empty && age != null) { setText(age.toString()); setStyle("-fx-text-fill: " + (age.intValue() < 18 ? "red" : "black")); } } });
10
Code comparison TornadoFX: Custom TableCell
column("Age", Customer::ageProperty).cellFormat { -> age text = "$age" style { textFill = if (age.toInt() < 18) RED else BLACK } } CSS property (textFill) is type safe, CSS value (Color) is type safe CellFormat creates a CellFactory and a Cell and implements updateItem Text is assigned with a String template
11
TornadoFX Building Blocks
12
Type Safe Builders Construct hierarchy of Nodes Common properties as params Function block operates on the created Node borderpane { top { toolbar { button("Refresh").action { onRefresh() } } } } Creates and attaches nodes to it’s parent But no Scene Builder for builders.. Yet!
13
View class MyView : View() {
Container for the root Node Contains UI logic Optionally delegates business logic to Controller or ViewModel Views are Singletons within a Scope - Fragments are not Can be embedded in other components or Views, or be the scene root Hot reloading during development class MyView : View() { override val root = vbox { button("Click me").action { information("You clicked me!") } } }
14
Controller Contains shared state and business logic Inject into any other Component Singletons within the current Scope class CustomerController : Controller() { val api: Rest by inject() fun listCustomers(): ObservableList<Customer> = api.get("customers").list().toModel() }
15
Model class Person { val firstNameProperty = SimpleStringProperty()
var firstName by firstNameProperty val lastNameProperty = SimpleStringProperty() var lastName by lastNameProperty }
16
ViewModel Mediator between View and Model Rollback / Commit
Validation / Decoration Can contain business logic ItemViewModel class PersonModel : ItemViewModel<Person>() { val firstName = bind(Person::firstNameProperty) val lastName = bind(Person::lastNameProperty) }
17
Scopes Enables separation for JPro apps Perfect for MDI
Components are unique only within the scope Easy grouping of UI and state Enables testing
18
Code Examples
19
Forms class LoginForm : View("Login") { val user = UserModel() override val root = form { fieldset(title, labelPosition = VERTICAL) { field("Username") { textfield(user.username).required() } field("Password") { passwordfield(user.password).required() } button("Log in") { enableWhen(user.valid) action(user::login) } } } } No boilerplate, every word expresses intent. Forms can also be created with FXML.
20
FXML Views can load the root Node from FXML
The View becomes the Controller class MyView : View() { val person: PersonModel by inject() override val root = toolbar { label("Name:") textfield(person.name) button(“Save").action(person::save) } } class MyView : View() { val person: PersonModel by inject() override val root: ToolBar by fxml() val name: TextField by fxid() init { name.bind(person.name) } fun save() = person.save() } <ToolBar xmlns=" xmlns:fx=" <Label text="Name:"/> <TextField fx:id="name"/> <Button text="Save" onAction="#save"/> </ToolBar> annotation, but you don’t need it Builder parameters can be named to make it clearer
21
Type Safe CSS label("Hello TornadoFX").addClass(heading)
val mainColor = c("#bdbd22") val heading by cssclass() heading { textFill = mainColor fontSize = 20.px fontWeight = BOLD } button { padding = box(10.px, 20.px) borderColor += box(Color.DARKGRAY) Inspired by type safe UI builders Extremely refactorable Covers 95% of the JavaFX CSS support Variables and even functions Color.derive/ladder CSS hot reload The rest can be described as Strings, or you can combine with a plain old css file CSS properties and elements are discoverable with code completion label("Hello TornadoFX").addClass(heading)
22
Charts piechart("Imported Fruits") { data("Grapefruit", 12.0)
data("Oranges", 25.0) data("Plums", 10.0) data("Pears", 22.0) data("Apples", 30.0) } barchart("Stock Monitoring, 2010", CategoryAxis(), NumberAxis()) { series("Portfolio 1") { data("Jan", 23) data("Feb", 14) data("Mar", 15) } series("Portfolio 2") { data("Jan", 11) data("Feb", 19) data("Mar", 27)
23
OSGi Includes OSGi metadata Built in OSGi console
Allows reloading JavaFX apps Dynamic Views Dynamic Stylesheets
24
Workspace Turns TornadoFX into an RCP platform
Browser like navigation history (optional tabbed navigation) Common action buttons connected to currently docked View Optional drawers on all sides Views can provide dynamic or static additions to the Workspace Save button -> onSave() Refresh button -> onRefresh() Delete button -> onDelete() Button states connected to savable, refreshable, deletable Default global button states can be configured, then overridden per View
25
IntelliJ IDEA Plugin Generate code Run configurations Intentions
Run Views directly Hot reload of Views and/or CSS Intentions Custom editors (Ex Type Safe CSS color picker)
26
TornadoFX Community and Resources
GitHub tornadofx.io/github Slack tornadofx.io/slack Guide tornadofx.io/guide YouTube tornadofx.io/screencasts Slack is very active, we use it more than GitHub/Issues Features are added based on discussions and use cases
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.