Lightweight JavaFX Framework for Kotlin
- Dependency injection
- Type safe GUI builders
- First class FXML support
- Type safe CSS builders
- Async task execution
- Hot reload of Views and Stylesheets
- REST client with automatic JSON conversion
- Zero config, no XML, no annotations
- Screencast introduction
- Wiki
- Slack
- User Forum
- Dev Forum
- Stack Overflow
- Documentation
- IntelliJ IDEA Plugin
- Example Application
- Maven QuickStart Archetype
- Changelog
mvn archetype:generate -DarchetypeGroupId=no.tornado \
-DarchetypeArtifactId=tornadofx-quickstart-archetype \
-DarchetypeVersion=1.0.3
Remember to update version to 1.4.6 in pom.xml
<dependency>
<groupId>no.tornado</groupId>
<artifactId>tornadofx</artifactId>
<version>1.4.6</version>
</dependency>
compile 'no.tornado:tornadofx:1.4.6'
Create a View
class HelloWorld : View() {
override val root = HBox(Label("Hello world"))
}
Load the root node from HelloWorld.fxml
and inject controls by fx:id
class HelloWorld : View() {
override val root: HBox by fxml()
val myLabel: Label by fxid()
init {
myLabel.text = "Hello world"
}
}
Start your application and show the primary View
by extending the App
class
class HelloWorldApp : App {
override val primaryView = HelloWorld::class
init {
importStylesheet("/styles.css")
}
}
Start app and load a stylesheet
Use Type Safe Builders to quickly create complex user interfaces
class MyView : View() {
override val root = VBox()
private val persons = FXCollections.observableArrayList<Person>(
Person(1, "Samantha Stuart", LocalDate.of(1981,12,4)),
Person(2, "Tom Marks", LocalDate.of(2001,1,23)),
Person(3, "Stuart Gills", LocalDate.of(1989,5,23)),
Person(3, "Nicole Williams", LocalDate.of(1998,8,11))
)
init {
with(root) {
tableview(persons) {
column("ID", Person::id)
column("Name", Person::name)
column("Birthday", Person::birthday)
column("Age", Person::age)
}
}
}
}
RENDERED UI
Create a Customer model object that can be converted to and from JSON and complies with JavaFX Property guidelines:
class Customer : JsonModel {
var id by property<Int>()
fun idProperty() = getProperty(Customer::id)
var name by property<String>()
fun nameProperty() = getProperty(Customer::name)
override fun updateModel(json: JsonObject) {
with(json) {
id = int("id")
name = string("name")
}
}
override fun toJSON(json: JsonBuilder) {
with(json) {
add("id", id)
add("name", name)
}
}
}
Create a controller which downloads a JSON list of customers with the REST api:
class HelloWorldController : Controller() {
val api : Rest by inject()
fun loadCustomers(): ObservableList<Customer> =
api.get("customers").list().toModel()
}
Configure the REST API with a base URI and Basic Authentication:
with (api) {
baseURI = "http://contoso.com/api"
setBasicAuth("user", "secret")
}
Load customers in the background and update a TableView on the UI thread:
runAsync {
controller.loadCustomers()
} ui {
customerTable.items = it
}
Load customers and apply to table declaratively:
customerTable.asyncItems { controller.loadCustomers() }
Define a type safe CSS stylesheet:
class Styles : Stylesheet() {
companion object {
// Define css classes
val heading by cssclass()
// Define colors
val mainColor = c("#bdbd22")
}
init {
s(heading) {
textFill = mainColor
fontSize = 20.px
fontWeight = BOLD
}
val flat = mixin {
backgroundInsets = box(0.px)
borderColor = box(Color.DARKGRAY)
}
s(button) {
padding = box(10.px, 20.px)
fontWeight = BOLD
}
s(button, textInput) {
+flat
}
}
}
Create an HBox with a Label and a TextField with type safe builders:
hbox {
label("Hello world") {
addClass(heading)
}
textfield {
promptText = "Enter your name"
}
}
Get and set per component configuration settings:
// set prefWidth from setting or default to 200.0
node.prefWidth(config.double("width", 200.0))
// set username and age, then save
with (config) {
set("username", "john")
set("age", 30)
save()
}
Create a Fragment
instead of a View
. A Fragment
is not a Singleton
like View
is, so you will
create a new instance and you can reuse the Fragment in multiple ui locations simultaneously.
class MyFragment : Fragment() {
override val root = Hbox(..)
}
Open it in a Modal Window:
MyFragment().openModal()
Lookup and embed a View
inside another Pane
in one go
root += MyFragment::class
Inject a View
and embed inside another Pane
val myView: MyView by inject()
init {
root += myFragment
}