Skip to content
Hendra Anggrian edited this page Nov 21, 2019 · 6 revisions

Getting started

Why Ktfx Layouts

Ktfx Layouts provides support to write JavaFX layouts (Node, MenuItem, PathElement, etc.) with Kotlin DSL. The aim here is to avoid the long code that are usually associated with FXML which is the standard way to produce JavaFX layouts.

While you can create UI programmatically, it's hardly done because it's somewhat ugly and hard to maintain. Here's a plain Kotlin version (one in Java is even longer):

val box = VBox()
val name = TextField()
val button = Button()
button.text = "Say Hello"
button.setOnClickListener {
    Alert(AlertType.NONE, "Hello").showAndWait()
}
layout.addView(name)
layout.addView(button)

A DSL makes the same logic easy to read, easy to write and there is no runtime overhead. Here it is again:

vbox {
    val name = textField()
    val button = button("Say Hello") {
        alert("Hello")
    }
}

Supporting custom controls

Layouts addition is not limited to JavaFX-provided controls. To add custom controls, simply call addNode (or addMenuItem, addElement, etc.) within the parent dsl.

pane {
    label("Here's my custom control")
    addNode(AwesomeControl()) {
        awesomeProperty().set(true)
    }
}

class AwesomeControl : Node {

    fun awesomeProperty(): BooleanProperty
}

Layout constraints

Some node containers have abilities to modify their child nodes behavior within the container. Naturally, the functions to modify these behaviors are embedded within the layout class. For example, consider this AnchorPane with Node.anchorBottom property.

anchorPane {
    val sendButton = button("Send")
    button.anchorBottom = 10.0
}

This would work just fine. However, due to the existence of dynamic layout dsl marker, it is impossible to call Node.anchorAll within button dsl.

anchorPane {
    val sendButton = button("Send") {
        anchorBottom = 10.0 // compile-time error
    }
}

To avoid this issue, applying constraints could be done with infix operators.

anchorPane {
    val sendButton = button("Send") anchorBottom 10.0
}

Anchor

Only seen in AnchorPane. It allows the edges of child nodes to be anchored to an offset from the anchor pane's edges.

anchorPane {
    val topRightLabel = label() anchorTop 0.0 anchorRight 0.0
}

Align

Seen in BorderPane, GridPane, StackPane and TilePane. It allows the position of child nodes to be aligned within the area.

stackPane {
    val webView = webView() align Pos.CENTER_LEFT
}

Margin

Seen in BorderPane, FlowPane, GridPane, HBox, StackPane, TilePane and VBox. It sets distance between child nodes.

hbox {
    label("Password")
    val field = passwordField { promptText = "Password" } marginLeft 10.0
}

Grow

Seen in GridPane, HBox, and VBox. It determines whether child nodes should take over the remaining area of this layout.

vbox {
    label("Your friends")
    val list = listView<Friend>(myFriends.toObservableList()) vgrow true
}

Grid

Only seen in GridPane. It specifies child nodes position in row & column.

gridPane {
    label("Email") row 0 col 0
    val emailField = textField() row 0 col 1
    label("Confirm email") row 1 col 0
    val confirmEmailField = textField() row 1 col 1
    val confirmButton = button("Confirm") row 2 col (0 to 2)
}

Notice that confirmButton col (0 to 2) accepts kotlin.Pair, in which case the first value is column index, followed by column span. This would mean the button would take the whole width.