Frontend Development with JavaFX and Kotlin: Build State-of-the-Art Kotlin GUI Applications Peter Späth all chapter instant download
Frontend Development with JavaFX and Kotlin: Build State-of-the-Art Kotlin GUI Applications Peter Späth all chapter instant download
com
https://ebookmass.com/product/frontend-development-with-
javafx-and-kotlin-build-state-of-the-art-kotlin-gui-
applications-peter-spath/
OR CLICK HERE
DOWLOAD NOW
https://ebookmass.com/product/beginning-kotlin-build-applications-
with-better-code-productivity-and-performance-ted-hagos/
ebookmass.com
https://ebookmass.com/product/programming-kotlin-applications-
building-mobile-and-server-side-applications-with-kotlin-brett-
mclaughlin/
ebookmass.com
https://ebookmass.com/product/etextbook-pdf-for-philosophy-here-and-
now-powerful-ideas-in-everyday-life-2nd-edition/
ebookmass.com
Maturing the Snowflake Data Cloud: A Templated Approach to
Delivering and Governing Snowflake in Large Enterprises
Andrew Carruthers
https://ebookmass.com/product/maturing-the-snowflake-data-cloud-a-
templated-approach-to-delivering-and-governing-snowflake-in-large-
enterprises-andrew-carruthers/
ebookmass.com
https://ebookmass.com/product/depression-3rd-edition-edition-raymond-
w-lam/
ebookmass.com
The Only EKG Book You’ll Ever Need Eighth Edition – Ebook
PDF Version
https://ebookmass.com/product/the-only-ekg-book-youll-ever-need-
eighth-edition-ebook-pdf-version/
ebookmass.com
https://ebookmass.com/product/yours-to-take-dixon-creek-ranch-
book-1-a-small-town-virgin-romance-emily-silver/
ebookmass.com
Frontend
Development with
JavaFX and Kotlin
Build State-of-the-Art Kotlin
GUI Applications
—
Peter Späth
Frontend Development with JavaFX and Kotlin
Peter Späth
Frontend Development
with JavaFX and Kotlin
Build State-of-the-Art Kotlin GUI Applications
Peter Späth
Leipzig, Sachsen, Germany
Distributed to the book trade worldwide by Apress Media, LLC, 1 New York Plaza, New York, NY 10004, U.S.A. Phone 1-
800-SPRINGER, fax (201) 348-4505, e-mail orders-ny@springer-sbm.com, or visit www.springeronline.com. Apress Media,
LLC is a California LLC, and the sole member (owner) is Springer Science + Business Media Finance Inc. (SSBM Finance
Inc). SSBM Finance Inc. is a Delaware corporation.
For information on translations, please e-mail booktranslations@springernature.com; for reprint, paperback, or audio rights,
please e-mail bookpermissions@springernature.com.
Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook versions and licenses are also
available for most titles. For more information, reference our Print and eBook Bulk Sales web page at http://www.apress.com/
bulk-sales.
Any source code or other supplementary material referenced by the author in this book is available to readers on GitHub (https://
github.com/Apress). For more detailed information, please visit https://www.apress.com/gp/services/source-code.
1 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Gradle for JavaFX and Kotlin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
A HelloWorld Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Setting Up for Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Setting Up for IntelliJ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Kotlin and Java Interoperability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
A Note About Kotlin Utilities for JavaFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
A Note About FXML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
A Note About Downloading JavaFX Releases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Build Setup for This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2 Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Why you Should use Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
One-Way and Two-Way Bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Custom Bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
About Observable Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3 Stages and Scenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
About Screens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Using Stages and the Application Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Dialog-Like Stages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
The JavaFX Application Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
About Scenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Position and Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Camera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Mnemonic and Accelerators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Focus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Node Lookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Snapshots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Fill and Other Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Keyboard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Mouse Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Mouse Event Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
v
vi Contents
7 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
What Events Are and Event Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Event Handlers and Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Drag and Drop Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
8 Effects and Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
About Effects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Animating Your Scenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
Transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Timeline Animations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
9 Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
The JavaFX Concurrency Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
About Kotlin Coroutines for JavaFX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
About the Author
Peter Späth graduated in 2002 as a physicist and soon afterward became an IT consultant, mainly for
Java-related projects. In 2016, he decided to concentrate on writing books on various aspects, but with
a main focus on software development. With two books about graphics and sound processing, three
books on Android app development, and a couple of books about Java, Jakarta EE, and Kotlin, Peter
continues his effort in writing software development-related literature.
ix
About the Technical Reviewer
xi
Introduction
Building elegant and highly responsible, responsive, and stable Java client applications (fat clients)
is a highly acceptable approach if security considerations or network availability speaks against
web applications, or maintaining servers and server applications lies out of scope for your project.
Additionally, using Kotlin as a programming language boosts code expressiveness and maintainability,
allowing for a development yielding a clean code approach.
The book introduces JavaFX as a frontend technology and from the very beginning focuses
on using Kotlin instead of Java for coding the program artifacts. Many listings and code snippets
accompany the text, readily allowing for a hands-on learning style.
The book is for low- to mid-level Java or Kotlin developers with or without JavaFX experience,
wishing to learn how to build JavaFX applications with Kotlin.
The readers will in the end be able to use Kotlin as a language for building basic to moderately
advanced and elaborated apps targeting JavaFX.
Any experience in using JavaFX and frontend coding is not a requirement for reading the book.
Being a Kotlin expert is not necessary either, but having read introductory-level books or studied
online resources is surely helpful. The online documentation of Kotlin and JavaFX also provides
valuable resources you can use as a reference while reading this book.
Source Code
All source code shown or referred to in this book can be found at github.com/apress/frontend-
development-javafx-kotlin.
This book should be read sequentially to get the most benefit from it. Of course, you can skip one
or the other chapter if you already gained knowledge elsewhere. Taking its introductory nature, the
book is not meant to present a reference fully covering each and every aspect of Kotlin frontend
programming or JavaFX, so also consulting the online documentation at
https://openjfx.io/
https://openjfx.io/javadoc/19/
https://kotlinlang.org/docs/home.html
xiii
xiv Introduction
while you are reading the book certainly is not a bad idea.
The book is split up into nine chapters. Chapter 1 gives a general introduction and presents hello
world-style programs for Gradle, Eclipse, and IntelliJ.
Chapter 2 talks about using properties as data holders and addresses one- and two-way binding
techniques for connecting controls and data in your program.
Chapter 3 introduces stages and scenes, which serve as primordial containers for visual artifacts.
Chapter 4 talks about containers and ways to lay out and style your scenes.
Chapter 5 handles nodes and controls including styling. These aspects usually constitute the biggest
part of your project work speaking of time budget.
Chapter 6 presents lists and tables, which are particularly important for enterprise-level projects.
Chapter 7 is for summarizing and deepening our knowledge about event handling in JavaFX. This
also includes drag and drop procedures.
Chapter 8 introduces effects and animation, improving user experience and giving your programs
some eye candies.
As a prospect, Chapter 9 briefly introduces concurrency techniques, giving you a starting point for
handling background processing needs.
Getting Started
1
In this chapter, we give a brief introduction to using JavaFX and Kotlin together, and we create “Hello
World”–style projects for the command line, for Eclipse, and for IntelliJ IDEA.
Introduction
JavaFX is the dedicated fat client (desktop application) GUI toolkit for current Java releases. It is
the replacement and successor of the venerable Java Swing technology. This switch happened around
2010, and since then JavaFX has been constantly improved and extended. With JREs up to version
JDK 9, JavaFX was part of the Java distribution—with JDK 11 and later, it has to be installed
separately.
The following features describe JavaFX:
. Built-in controls: Labels, editable text fields, buttons, combo boxes, checkboxes, radio buttons,
menu bars, scrollbars, accordion, tabs, canvas (for drawing shapes and figures), color picker, pag-
ination, 3D graphics (games, science, product presentation), WebView (presenting and interacting
with web contents), dialogs, sliders, spinners, progress bars
. Lists, tables, trees
. Built-in layouts: AnchorPane (anchoring nodes to one of the edges or to the center point),
BorderPane (placing nodes at bottom, top, right, left, center), FlowPane (placing nodes consec-
utively and wrapping at the boundaries), TilePane (same as FlowPane, but with all cells the same
size), GridPane (placing nodes in a grid with cell sizes dynamically calculated and on demand
spanning several rows and columns), VBox (placing nodes in columns), HBox (placing nodes in
rows), StackPane (placing nodes in an overlay fashion)
. Animation (fade, fill, stroke, translate, rotate, scale, . . . ), effects (glow, blend, bloom, blur,
reflection, sepia, shadow, lighting)
. Nodes stylable via CSS
. Some built-in chart widgets
. Flexible and concise data binding via observable properties
. Descriptive layouting via FXML
. Module support (for JDK 9+)
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023 1
P. Späth, Frontend Development with JavaFX and Kotlin,
https://doi.org/10.1007/978-1-4842-9717-9_1
2 1 Getting Started
In this book, we describe a subset of these features, giving you a starting point for your own
projects.
Using Kotlin as a programming language instead of Java gives a boost to your coding experience.
Just to give you an example, consider a button with a click handler. In Java, you’d write
Button btn = new Button();
btn.setText("Say 'Hello World'");
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
With 142 characters, this is more than 40% shorter than the Java variant! And besides being shorter,
it is also more expressive and by that easier to understand and easier to maintain.
Using some sufficiently nonobtrusive utility functions, this can even be further reduced to 81
characters in size:
val btn = Button("Say 'Hello World'") {
println("Hello World!")
}
As a build tool, we use Gradle from https://gradle.org/. It is highly flexible, works on any operating
system that provides a Java installation, and by means of plugins or preinstalled components can be
operated from many IDEs.
I first describe the CLI mode for running Gradle builds. This is how you would use it in a server
environment, but it also serves as a good starting point if you want to learn how to use Gradle inside
an IDE workflow.
If not already present, get and install a version 17 JDK. Throughout the book, we will be using
OpenJDK 17, but if chances are good you can also take Oracle’s supported JDK 17 or a higher version
from either Oracle or https://openjdk.org/ without any problems possibly coming up.
Gradle for JavaFX and Kotlin 3
Note Using Oracle’s JDK 17 or higher requires buying a license if you plan to use it for a longer
term; see www.oracle.com/java/.
As a next step, fetch Gradle from https://gradle.org. In this book, we use version 7.6 from https://
gradle.org/next-steps/?version=7.6&format=bin. In order to announce Java to Gradle, either make
sure java and javac (with .bat extension on Windows) are in your PATH, or you have the
environment variable JAVA_HOME point to your JDK installation folder (recommended). To simplify
using Gradle, you can also put GRADLE-INST-DIR/bin (with GRADLE-INST-DIR pointing to your
Gradle folder), or GRADLE-INST-DIR\bin for Windows, on the path.
Note In Linux, environment variables like PATH or JAVA_HOME get set via
export PATH=/bin:/usr/bin:/path/to/my/gradle/bin.
In Windows, you must use the system settings dialog.
Kotlin: 1.7.10
Groovy: 3.0.13
Ant: Apache Ant(TM) version 1.10.11 compiled on
July 10 2021
JVM: 17.0.1 (Oracle Corporation 17.0.1+12-39)
OS: Linux 5.15.0-56-generic amd64
Important is the “JVM:” line. The Kotlin version shown does not mean you would not be able to build
applications running under a different Kotlin version—it just tells it is using Kotlin 1.7.10 for its own
purposes.
Next, create a project folder anywhere on your system. For our example project, we call it
HelloWorld. Change into that folder:
cd /path/to/HelloWorld (Linux)
chdir C:\path\to\HelloWorld (Windows)
You can also enter just gradle init, but then you will subsequently be asked for project
coordinates inside the terminal.
4 1 Getting Started
The “init” task creates a simple scaffold project which consists of a main project described by
file settings.gradle and a subproject called “app” in the accordingly named subfolder. The
application can be run by just entering either of
gradle app:run
gradle run
The second variant is possible, because there is just one subproject. By the way, you can list all
possible tasks via gradle tasks or gradle tasks --all, and entering gradle help shows
more info.
Did you notice that two executable files gradlew and gradlew.bat and a folder gradle were
created? This is the Gradle Wrapper, and it is a Gradle installation on its own, and you can henceforth
use it to build the project. Just use gradlew from the wrapper instead of gradle from the Gradle
distribution. You can even delete the main Gradle installation folder at this time, if you like.
It is now time to add JavaFX to the project. In Gradle, the build.gradle file is the main
configuration file for the build process. You can find it inside the app subproject inside the app folder.
Open the file inside a text editor, and inside the plugins { . . . } section, add
plugins {
...
id 'org.openjfx.javafxplugin' version '0.0.13'
}
This plugin adds almost all that is necessary to add JavaFX to a Java or Kotlin project. Kotlin
capabilities were already added during gradle init. We however still need to make sure that
Kotlin compiles for JDK 17 and that JavaFX uses version 19 and allows for using the modules
“javafx.controls” and “javafx.graphics”. For that aim, add at the end of build.gradle
compileKotlin {
kotlinOptions {
suppressWarnings = true
jvmTarget = "17"
}
}
javafx {
version = "19"
modules("javafx.controls", "javafx.graphics")
}
Note JavaFX is separated into different modules. The modules “javafx.base”, “javafx.controls”, and
“javafx.graphics” are essential to almost any JavaFX application. Because both the controls and the
graphics module require the base module, the latter gets implicitly included in any build and can be
omitted from the modules list. For more details, see https://openjfx.io/javadoc/19/
In the next section, we code our little “Hello World” JavaFX with Kotlin application.
A HelloWorld Project
The scaffold project built via gradle init just prints “Hello World!” on the console if run. As a
starter JavaFX project, we instead want to show a little window with a button on it reacting to press
events. To do so, replace the contents of
app/src/main/kotlin/book/kotlinfx/App.kt
Setting Up for Eclipse 5
by
package book.kotlinfx
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage
fun main(args:Array<String>) {
Application.launch(HelloWorld::class.java, *args)
}
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
Note You can skip this section if you don’t use Eclipse.
Download and install a recent Eclipse IDE from www.eclipse.org/downloads/. Start Eclipse and then,
at Window → Preferences → Java → Installed JREs, register a JDK version 17 and make it the
default. See Figure 1-2.
Then, at File → New → Project. . . → Gradle → Gradle Project, create a new Gradle project. Once
asked, enter “kotlinfx” as the project’s name; see Figure 1-3.
Keep everything else at its defaults. You end up with a main and a subproject; see Figure 1-4.
The name of the subproject reads “lib.” We want to change it to a more meaningful variant.
6 1 Getting Started
Caution Due to a design issue inside the Gradle-Plugin for Eclipse 2022-12, you cannot rename the
subproject’s name via Mouse-Right → Refactor → Rename. . . We must apply a workaround.
Setting Up for Eclipse 7
Now delete the “lib” subproject from Eclipse. Make sure the “Also delete project contents” checkbox
is not checked.
In your system’s file explorer, rename folder lib inside WORKSPACE/kotlinfx to
HelloWorld.
On the main project, invoke Mouse-Right → Configure → Configure and Detect Nested
Projects. . . Press the “Finish” button. Ignore possibly shown errors.
Just to be on the safe side, restart Eclipse. The package view should now be as shown in
Figure 1-5.
Back to the application, replace the contents of the build.gradle file by
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.7.10'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.13'
}
repositories {
mavenCentral()
8 1 Getting Started
dependencies {
}
application {
mainClass = 'book.kotlinfx.AppKt'
}
compileKotlin {
kotlinOptions {
suppressWarnings = true
jvmTarget = "17"
}
}
javafx {
version = "19"
modules("javafx.controls", "javafx.graphics")
}
After changes to file build.gradle, the project regularly needs to be updated: on “kotlinfx,”
press Mouse-Right → Gradle → Refresh Gradle Project. Also, remove the packages inside src/
test/java; we don’t need them for now.
A fresh Eclipse installation doesn’t know how to handle Kotlin files. To fix this, open Help →
Eclipse Marketplace. . . Enter “kotlin” in the search field, and select to install “Kotlin Plugin for
Eclipse” from the search result list. Restart Eclipse twice.
Make a new folder src/main/kotlin and register it as a source folder via Mouse-Right →
Java Build Path → Source → Add folder. . . See Figure 1-6.
Inside the Kotlin sources section, add a package book.kotlinfx and inside it a Kotlin file
App.kt with contents:
package book.kotlinfx
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage
fun main(args:Array<String>) {
Application.launch(HelloWorld::class.java, *args)
}
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
You can now start the application inside the “Gradle Tasks” view at kotlinfx → HelloWorld →
application → run; see Figure 1-7.
After any changes to the coding, just invoke this task again. Gradle automatically takes care of
compilation and rebuilding the project artifacts necessary to run the updated application.
Note You can skip this section if you don’t use IntelliJ IDEA.
IntelliJ IDEA can be purchased at www.jetbrains.com/idea/, but you can also download the com-
munity edition, which comes at no cost. To start developing a JavaFX via Kotlin project in IntelliJ
IDEA, create a new project and select the “JavaFX” generator. As project name, enter “HelloWorld”;
as location, choose any folder at your discretion. Then select “Kotlin” as the language and “Gradle”
as the build system, enter “book.kotlinfx” as Group ID and “HelloWorld” as Artifact ID, and select a
version 17 JDK. See Figure 1-8.
Setting Up for IntelliJ 11
If you see a classcast exception as shown in Figure 1-9, open the build.gradle file and add
inside the plugins { . . . } section:
plugins {
...
id 'org.javamodularity.moduleplugin' version '1.8.12'
}
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage
fun main(args:Array<String>) {
Application.launch(HelloWorld::class.java, *args)
}
override
fun start(primaryStage:Stage) {
primaryStage.title = "Hello World!"
val btn = Button().apply {
text = "Say 'Hello World'"
setOnAction { _ ->
println("Hello World!")
}
}
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
After you open the “Gradle” tab, you can now start the application at HelloWorld → Tasks →
application → run. See Figure 1-10.
Kotlin sits on top of the JVM (Java Virtual Machine) and as such is designed to access any Java
library, including extensions of JavaFX. This way, it is possible to enhance your JavaFX projects in
various ways, such as adding math and statistics libraries, XML and JSON processing, networking,
cryptography, and what else you might think of.
14 1 Getting Started
Almost any library you want to include gets configured in the dependencies { . . . } section
of the build.gradle file. The Gradle documentation tells you more about that.
For example, to add the Apache Commons Math library, you would write inside build.gradle:
dependencies {
...
implementation 'org.apache.commons:commons-math3:3.6.1'
}
Note In Eclipse, you afterward have to invoke Mouse-Right .→ Grade .→ Refresh Gradle Project on
the project.
This book presenting JavaFX and Kotlin at an introductory level, the inclined reader is asked to
perform their own research on possible extensions.
In Kotlin, it is possible to write functions and variables outside any class. For example, consider a file
src/main/kotlin/book/kotlinfx/aaa.kt:
package book.kotlinfx
fun abc() {
println(msg)
}
import book.kotlinfx.msg
import book.kotlinfx.abc
// or: import book.kotlinfx.*
...
abc()
...
Internally, Kotlin generates a class named after the file, class book.kotlinfx.AaaKt.class
for the example, and puts such seemingly orphaned variables and functions inside it.
While this comes handy under circumstances, it bears the risk of damaging your object-oriented
design. Even worse, you can use such non-class files to add fields and functions to existing classes in
a rather uncontrolled manner, thwarting the original purpose of such extended classes and making the
code unreadable.
We therefore use such non-class Kotlin files only under the following conditions:
. We don’t use them to avoid creating new classes (with new responsibilities).
. We use them to improve readability (comprehensiveness), not to add complexity.
. We don’t add more than maybe one or two such files to any project.
Consider the following example: to create a button and add a click handler to it, we have to write
val btn = Button().apply {
text = "Say 'Hello World'"
setOnAction { _ ->
println("Hello World!")
}
}
or
val btn = Button("Say 'Hello World'").apply {
setOnAction { _ ->
println("Hello World!")
}
}
Wouldn’t it be nice if we had a constructor taking the text and the event handler? Even if there is
no such constructor defined in JavaFX, it is possible in Kotlin to define such a new constructor. In
order to achieve that, we would write in a Kotlin file:
package book.kotlinfx.util
import javafx.scene.control.*
fun Button(label:String,
action: Button.() -> Unit):Button =
Button(label).apply{
setOnAction { _ -> action() }
}
This could, for example, be placed inside a file util.kt (the name doesn’t matter), inside package
book.kotlinfx.util.
For any class, we can now import the extension and use the new constructor:
package what.so.ever
import book.kotlinfx.util.*
...
val btn = Button("Click on me",{
println("Clicked")
})
...
Or, because in Kotlin you can write a trailing functional parameter outside the round brackets:
package what.so.ever
import book.kotlinfx.util.*
...
val btn = Button("Click on me"){
println("Clicked")
}
...
16 1 Getting Started
The little HelloWorld program from the previous section can be rewritten to
package book.kotlinfx.helloworld
import javafx.application.Application
import javafx.event.ActionEvent
import javafx.event.EventHandler
import javafx.scene.Scene
import javafx.scene.control.Button
import javafx.scene.layout.StackPane
import javafx.stage.Stage
import book.kotlinfx.util.*
fun main(args:Array<String>) {
Application.launch(HelloWorld::class.java, *args)
}
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
Throughout the whole book, we use no-class Kotlin files exactly for such kind of extensions.
Around 2012, Oracle introduced FXML, which is an XML-based description language for JavaFX. In
order to use it, you must add javafx.fxml to the modules list in build.gradle:
javafx {
version = "19"
modules(
"javafx.controls",
"javafx.graphics",
"javafx.fxml")
}
In the start() method of the application (see, e.g., the HelloWorld example earlier), you would
write
primaryStage.title = "Hello World!"
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<GridPane fx:controller="book.kotlinfx.ch01.MyController"
xmlns:fx="http://javafx.com/fxml"
alignment="center" hgap="10" vgap="10">
<padding>
<Insets top="25" right="25" bottom="10" left="25"/>
</padding>
<HBox spacing="10" alignment="bottom_right"
GridPane.columnIndex="1" GridPane.rowIndex="2">
<Button text="Click me"
onAction="#handleButtonAction"/>
</HBox>
<Text fx:id="actiontarget"
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
</GridPane>
@Suppress("UNUSED_PARAMETER")
@FXML fun handleButtonAction(event:ActionEvent) {
actiontarget?.setText("Button pressed")
}
}
Separating logic and design is a valid approach for good practices. So if you like, you can go ahead
and use FXML for your project. However, in this book, we don’t further apply FXML technologies
for the following reasons:
. There is nothing in FXML you can’t do with Kotlin. On the other hand, you cannot transcode all
programmatic constructs into XML.
. Defining animation or drag and drop operations in pure XML is complicated, if possible at all.
JavaFX internally uses a set of operating system–dependent native libraries. Usually, this happens
behind the scenes, and you don’t have to take care of how this works. Under circumstances however,
you might want to have more control over what JavaFX library and native library artifacts get used. To
achieve this, download an SDK following the links presented in https://openjfx.io/. After unzipping
the archive, the directory, for example, looks like
openjfx-19_linux-x64_bin-sdk
javafx-sdk-19
legal
lib
src.zip
or for Windows
javafx {
sdk = 'C:\\path\\to\\openjfx-19_win-x64_bin-sdk' +
'\\javafx-sdk-19'
modules("javafx.controls", "javafx.graphics", ...)
}
So the sdk variable must point to the parent folder of the lib directory. From here, you can build
and run Kotlin JavaFX applications as usual.
The sources you can download at http://todo TODO ENTER LINK consist of three parts:
. An empty Gradle project with each chapter as a subproject. You can use it for experiments and
if you prefer to enter the snippets presented in this book one after another. You can operate it via
Gradle, but Eclipse .project and .classpath files have been added, so you can immediately
load it as an Eclipse workspace.
. A Gradle project with each chapter as a subproject. Each subproject reflects the state you get after
you enter all the code snippets from a chapter. Alternatives that cannot be active at the same time for
technical reasons are commented out. You can operate it via Gradle, but again Eclipse .project
and .classpath files have been added, so you can immediately load it as an Eclipse workspace.
. A loose collection of the code snippets from the book.
Properties
2
Properties in JavaFX are data holders with other properties or third parties being able to register some
interest in being informed about value changes. This comes handy for user input and output controls
in a GUI, where the need for observing value changes and appropriately reacting to such changes is
obvious.
In JavaFX, there are following concrete types of properties:
– SimpleXxxProperty
Where “Xxx” stands for one of: Boolean, Integer, Long, Float, Double, String, Object, List, Set,
Map. Represents a read/write property. This means value changes propagate to clients like e.g. text
fields, but also receives client value updates like e.g. a user entering data into a text field.
– SimpleStyleableXxxProperty
Where “Xxx” stands for one of: Boolean, Integer, Long, Float, Double, String, Object. Represents
a read/write property targeting CSS values (node styles).
– ReadOnlyXxxWrapper
Where “Xxx” stands for one of: Boolean, Integer, Long, Float, Double, String, Object, List, Set,
Map. Represents a read-only property. This means direct value changes via setters are not possible.
This list shows the more often used classes for including properties in your code. For more
information and an exhaustive overview of all property related interfaces and classes see the
javafx.beans.property package at https://openjfx.io/javadoc/19/javafx.base/javafx/beans/
property/package-summary.html or https://docs.oracle.com/javase/8/javafx/api/index.html
All nodes in JavaFX (labels, text fields, checkboxes, sliders, . . . ) use properties to hold data. Via
binding you can connect node properties to properties in your code. We talk more about bindings
below.
Consider a text field and a button setting the text field’s value:
package book.kotlinfx.ch02
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023 19
P. Späth, Frontend Development with JavaFX and Kotlin,
https://doi.org/10.1007/978-1-4842-9717-9_2
20 2 Properties
fun main(args:Array<String>) {
Application.launch(App::class.java, *args)
}
val t = TextField("").apply{
textProperty().addListener {
_, oldValue, newValue ->
println("textfield changed from " +
oldValue + " to " + newValue)
// inform model...
} }
val b = Button("Set 42"){
t.textProperty().value = "42"
}
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
The util.kt file is listed in the appendix, but it is also included within the book’s sources provided
as a download.
A text field connected to the model layer of your application design via
val t = TextField("").apply{
textProperty().addListener { _, oldVal, newVal ->
// inform model...
} }
bears the risk that business logic sneaks into the view classes. For small projects it might be acceptable
to ignore this risk, but for larger projects it is preferable to abstract model changes from the frontend
controllers. This way we would be able to enforce the separation between the frontend layer and other
Why you Should use Properties 21
layers of the application, promoting a clean application design. For this aim we can use additional
properties in our code:
val text1Property = SimpleStringProperty("Some text")
// ...
What is now necessary is a way to connect UI controllers and such additional properties. JavaFX
provides such a connectivity via bindings:
val text1Property = SimpleStringProperty("Some text")
// ...
Such additional properties can be moved to an own class, commonly referred-to as viewmodel:
fun main(args:Array<String>) {
Application.launch(AppWithViewModel::class.java, *args)
}
class MyViewModel {
val text1Property = SimpleStringProperty("Some text")
}
val vm = MyViewModel()
with(primaryStage){
scene = Scene(root, 300.0, 250.0)
show()
}
}
}
Another advantage of such a separation is that the viewmodel can be totally agnostic of UI
components, greatly improving testability by letting testing code directly access viewmodel classes
and their properties.
Inside the viewmodel, you can then add business logic:
class MyViewModel {
val text1Property = SimpleStringProperty("Some text")
.apply { addListener { _, oldVal, newVal ->
// business logic...
} }
}
But you can of course factor out business logic and let other classes connect to the viewmodel for that
purpose:
val vm = ... // somehow fetch MyViewModel
vm.text1Property.addListener { _, oldVal, newVal ->
// business logic...
}
Using properties and bindings makes it astonishingly easy to bind one UI control to another. Consider
a slider controlling a circle’s radius. All that needs to be done is to bind the slider’s valueProperty
to the circle’s radius property:
...
val sl = Slider(0.0,30.0,20.0)
val circ = Circle().apply {
radiusProperty().bindBidirectional(
sl.valueProperty())
}
to connect properties to each other. You might have guessed that there is also an unidirectional binding.
In fact there is, and in order to use it you just have to write
prop1.bind(prop2) // unidirectional!
The main difference between unidirectional and bidirectional binding is, that with the former
variant changes from prop1 get propagated to prop2 and vice versa, while in prop1.bind(
prop2 ) changes from prop2 get propagated to prop1, but not the other way round. This is
important for user input controls like text edit fields, which should reflect changes in the underlying
property, but must of course propagate user input to the program as well:
val text1Property = SimpleStringProperty("Some text")
val text1 = TextField().apply{
textProperty().bindBidirectional(text1Property)
One-Way and Two-Way Bindings 23
For readonly controls like labels, user input is not possible, so an unidirectional binding suffices:
val text1Property = SimpleStringProperty("Some text")
val lab1 = Label().apply{
textProperty().bind(text1Property)
}
So then why not always use bidirectional bindings? First of all, bidirectional bindings sometimes
introduce overly complex data flows exhibiting unexpected behavior. Second, in unidirectional
bindings you can replace properties by binding expressions. For example, what if in
...
val sl = Slider(0.0,30.0,20.0)
val circ = Circle().apply {
radiusProperty().bind(
sl.valueProperty())
}
...
we want to change the slider range to 0.0 . . . 100.0, but leaving the circle radius range at 0.0 . . . 30.0?
For that we obviously need a way to multiply the property by 0.3 This is possible, and actually it is
easy:
...
val sl = Slider(0.0,100.0,20.0)
val circ = Circle().apply {
radiusProperty().bind(
sl.valueProperty().multiply(0.3))
}
...
get called binding expressions, and you can use them as a parameter to bind(), provided the type
matches. You cannot use them for bidirectional bindings, because in a + b c you can recompute c if
a or b changes, but in a + b .← c you cannot reliably recompute a and b, if c changes.
24 2 Properties
The following list shows an overview of all binding expressions. Note that a number property (Dou-
bleProperty, FloatProperty, LongProperty, IntegerProperty) is also an ObservableNumberValue. Any
property is an ObservableValue. A StringProperty is an ObservableStringValue. A BooleanProperty
is an ObservableBooleanValue.
– DoubleExpression
add(Double | Float | Long | Int | ObservableNumberValue)
.→DoubleExpression
negate()
.→DoubleExpression
– FloatExpression
add(), subtract(), multiply(), divide()
Same as for DoubleExpression, but returns
a FloatExpression if the other operand
does not correspond to a Double
negate()
.→FloatExpression
.→LongExpression
concat(Object)
.→StringExpression, but does not update if the
greaterThanOrEqualTo(String | ObservableStringValue)
.→BooleanExpression
lessThan(String | ObservableStringValue)
.→BooleanExpression
lessThanOrEqualTo(String | ObservableStringValue)
.→BooleanExpression
isEqualTo(String | ObservableStringValue)
.→BooleanExpression
isNotEqualTo(String | ObservableStringValue)
.→BooleanExpression
length()
.→IntegerExpression
– BooleanExpression
and(ObservableBooleanValue)
.→BooleanExpression
or(ObservableBooleanValue)
.→BooleanExpression
not()
.→BooleanExpression
26 2 Properties
asString()
.→StringExpression
isEqualTo(ObservableBooleanValue),
isNotEqualTo(ObservableBooleanValue)
.→BooleanExpression
Custom Bindings
Sometimes the binding expressions described in the previous section do not match our needs. In this
case the various static methods from the Bindings class (package javafx.beans.binding)
provide invaluable help. Especially the createObjectBinding() method is really powerful and
can be used to create almost any binding you might think of.
As an example consider a Slider bound to a DoubleProperty via bidirectional binding:
val prop = SimpleDoubleProperty()
// <- could go to ViewModel
val sl2 = Slider(0.0,30.0,20.0).apply{
valueProperty().bindBidirectional(prop)
}
Because the binding is bidirectional, a change of the property’s value would reflect in the slider’s
elongation and vice versa. The property could also go to a ViewModel, where it can be managed by
some data storage, or where other business logic can happen. Back in the view layer, we want the
property to connect to a circle, controlling its fill color. This can be accomplished by a binding object:
val colorBinding:ObservableValue<Color> =
Bindings.createObjectBinding( { ->
Color.color(0.0,0.0,0.0+prop.getValue()/30.0)
}, prop)
You can see that inside Bindings.createObjectBinding() any kind of object can be created.
It could just as well be another Double object, using some arbitrary formula.
Note The Bindings class contains many more static helper functions for various binding creations
and calculations. See https://openjfx.io/javadoc/19/javafx.base/javafx/beans/binding/Bindings.html
for details.
Java and Kotlin collections lack one important feature needed for frontend development: they have
no built-in mechanism to tell interested parties about additions, changes, or removal of elements. For
this reason JavaFX uses its own collection variants: ObservableSet, ObservableList, and
ObservableMap, all inside package javafx.collections (module javafx.base).
Such observable collections play a very important role throughout JavaFX. Apart from list views,
table views and tree views, there are many more classes that use or return observable collections.
About Observable Collections 27
In this section we talk about list views, just to give you some primary insight into how observable
collections are used inside JavaFX. At this point we don’t investigate such collection related views in
a sound fashion, though. This is left for a later chapter.
First we need to know how to create observable collections. If you look at the API documentation
of ObservableSet, ObservableList or ObservableMap, you can easily see that these are
interfaces. So we feel tempted to look for classes implementing the interfaces. However, there seem
to be no obvious candidates for that, so the question arises: how can we get instances of an observable
set, list or map? The designers of JavaFX decided to provide a factory class for that. This class is
called FXCollections, and you can find it in the same package as the interfaces, javafx.-
collections. The procedure goes as follows:
// Creating empty observable collections. String and
// Int as type parameters are only examples - use
// your own.
val s1 = FXCollections.emptyObservableSet<String>()
val l1 = FXCollections.emptyObservableList<String>()
val m1 = FXCollections.emptyObservableMap<Int,String>()
Class FXCollections provides many more static factory methods not shown here. For more
details see the API documentation at https://openjfx.io/javadoc/17/javafx.base/javafx/collections/
FXCollections.html.
Note There are also variants dealing with observable fixed size arrays instead of collections. We don’t
treat them in this book, but you can learn more about them here: https://openjfx.io/javadoc/17/javafx.
base/javafx/collections/ObservableArray.html, https://openjfx.io/javadoc/17/javafx.base/javafx/collec
tions/ObservableFloatArray.html and https://openjfx.io/javadoc/17/javafx.base/javafx/collections/Obs
ervableIntegerArray.html
val gp = GridPane().apply {
padding = Insets(5.0)
hgap = 5.0
add( listView, 0,0)
28 2 Properties
You can see from this example, that for altering the view contents we don’t have to talk to the
ListView instance. Instead we directly address the observable collection, thus making it easy to
extract the view data model to a different application layer, like for example a ViewModel.
In case we want to react to changes in the observable collection, which could for example happen if
the user entered data into a view that has been made editable beforehand, we have to install a change
listener to the collection. This is not an easy task, because sets, lists and maps behave differently
if elements get added, replaced, removed or shuffled around, and we have to take care of several
elements changing at once by some operation.
The easiest case is a listener for an observable set. You register it via
val os:ObservableSet<Int> = ... // or whatever type
val cl = ... (make a SetChangeListener<Int>)
os.addListener(cl)
Since a SetChangeListener has a single method interface (SAM = Single Abstract Method), we
can also directly use a lambda construct as seen in the following example:
val s3 = FXCollections.observableSet(
mutableSetOf(1,2,3))
You can see that the listener only tells about single elements being added or removed. For bulk
operations like .addAll(...) the listener simply gets invoked several times.
Just a little more complex are listeners for ObservableMap instances. We need to be informed
about the addition or removal of key/value pairs, but also about just a value change for a given key:
val m3 = FXCollections.observableMap(
mutableMapOf(1 to "a", 2 to "b", 3 to "c"))
m3.addListener { chg:
MapChangeListener.Change<out Int,out String> ->
if (chg.wasRemoved() && chg.wasAdded()) {
println("Replaced in map: (${chg.key}, "+
"${chg.valueRemoved} -> ${chg.valueAdded})")
} else {
if (chg.wasRemoved()) {
println("Removed from map: (${chg.key}, "+
"${chg.valueRemoved})")
} else if (chg.wasAdded()) {
println("Added to map: (${chg.key}, "+
"${chg.valueAdded})")
}
}
}
About Observable Collections 29
The most important use case of observed collections are observed lists. This is because for ListView
and TableView nodes what exactly is needed are observable lists to hold the data. Not surprisingly,
list change listeners thus are rather elaborated. The current implementation allows for detecting:
Also, contrary to the other listeners described above, the change object provides an iterator you
have to loop through to fetch all changes. The following listing shows an example:
val l3 = FXCollections.observableList(
mutableListOf(1,2,3))
l3.addListener( { chg:
ListChangeListener.Change<out Int> ->
while (chg.next()) {
if (chg.wasPermutated()) {
println("Permutated: " + (chg.from..chg.to-1))
(chg.from..chg.to-1).forEach{ i ->
val newIndex = chg.getPermutation(i)
println("index[${i}] moved "+
"to index[${newIndex}]")
}
} else if (chg.wasUpdated()) {
println("Updated: " + (chg.from..chg.to-1))
println("Updated elements: " + chg.list.
subList(chg.from, chg.to))
} else if (chg.wasReplaced()) {
println("Replaced: " + (chg.from..chg.to-1))
println("Removed Size: " + chg.removedSize)
println("Removed List: " + chg.removed)
println("Added Size: " + chg.addedSize)
println("Added List: " + chg.addedSubList)
} else if (chg.wasRemoved()) {
println("Removed: " + (chg.from..chg.to-1))
println("Removed Size: " + chg.removedSize)
println("Removed List: " + chg.removed)
} else if (chg.wasAdded()) {
println("Added: " + (chg.from..chg.to-1))
println("Added Size: " + chg.addedSize)
println("Added List: " + chg.addedSubList)
}
}
})
l3.addAll(4,5,6)
l3.removeAt(2)
Random documents with unrelated
content Scribd suggests to you:
devant euls. Et conmenchièrent li archier, qui estoient entré en ces
estages, à traire fortement à ceuls qui se tenoient as deffenses; et
traioient si roit et si ouniement que à painnes ne se osoit nuls
amoustrer, se il n’estoit trop fort paveschiés. Entre ces deus
bierefrois qui estoient arestés devant les murs, avoit deus cens
compagnons à tout hauiaus et grans pils de fier pour effondrer le
mur; et jà en avoient des pières assés ostées et rompues, car li
archier qui estoient hault ens ès estages, les deffendoient de ject et
de tret. Fº 101.
P. 81, l. 26: cent.—Mss. A 20 à 22: deux cens. Fº 177 vº.
P. 82, l. 7: haviaus.—Mss. A 15 à 17, 20 à 22: hoiaux, hoyaux.
Fº 123.—Mss. A 23 à 29: houyaulx. Fº 139.
P. 82, l. 8: voir Sup. var. (n. d. t.)
Our website is not just a platform for buying books, but a bridge
connecting readers to the timeless values of culture and wisdom. With
an elegant, user-friendly interface and an intelligent search system,
we are committed to providing a quick and convenient shopping
experience. Additionally, our special promotions and home delivery
services ensure that you save time and fully enjoy the joy of reading.
ebookmass.com