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
- Example Application
- Maven QuickStart Archetype
- Changelog
mvn archetype:generate -DarchetypeGroupId=no.tornado \
-DarchetypeArtifactId=tornadofx-quickstart-archetype \
-DarchetypeVersion=1.0.3Remember 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 Viewis, 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::classInject a View and embed inside another Pane
val myView: MyView by inject()
init {
root += myFragment
}