Skip to content
This repository has been archived by the owner on Sep 28, 2024. It is now read-only.

Type Safe Builders

Thomas Nield edited this page Feb 28, 2016 · 31 revisions

WikiDocumentationType Safe Builders

Type Safe Builders

Builders are extension functions to the Java FX Pane class which enables you to create a new node, set some properties and add it to the children of the parent Pane with very little code. The hierarchical nature of the builders makes it easy to understand the ui composition with a simple glance.

There are builders for complex Node components as well, like TableView.

Builders also support automatic property binding for input type components like TextField, ComboBox etc.

###Example 1 ####A VBox containing two HBox components

Each HBox contains a Label and a TextField, both which utilize a margin constraint within the HBox. The TextField components are also set to use the maximum allowable width.

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            hbox {
                label("First Name") {
                    hboxConstraints { margin = Insets(5.0) }
                }
                textfield {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
            hbox {
                label("Last Name") {
                    hboxConstraints { margin = Insets(5.0) }
                }
                textfield {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
        }
    }
}

Rendered UI

Note also you can add Node instances to a builder without using builders for them. This is helpful if you have Node components existing outside your Builder or are working with Node items that don't have builder support. You can then call the Kotlin stdlib function apply() to then maintain the "builder flow".

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            hbox {
                this += Label("First Name").apply {
                    hboxConstraints { margin = Insets(5.0) }
                }
                this += TextField().apply {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
            hbox {
                this += Label("Last Name").apply {
                    hboxConstraints { margin = Insets(5.0) }
                }
                this += TextField().apply {
                    hboxConstraints { margin = Insets(5.0) }
                    useMaxWidth = true
                }
            }
        }
    }
}

##Example 2

A TableView

You can build an entire TableView, complete with custom cell renderers and column value mappings, with a succinct builder structure.

class MyView : View() {

    override val root = GridPane()

    init {
        with(root) {
            tableview<String> {
                gridpaneConstraints {
                    vhGrow = Priority.ALWAYS
                }
                items = FXCollections.observableArrayList("Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Zeta", "Eta")
                column<String>("String") { ReadOnlyStringWrapper(it.value) }
                column<Int>("Length", String::length)
                        .cellFormat {
                            if (it == 4) {
                                style = "-fx-background-color:#8b0000; -fx-text-fill:white"
                                text = it.toString()
                            } else {
                                text = it.toString()
                            }
                        }
            }
        }
    }
}

RENDERED UI

Note that values for columns can be mapped in two ways.

column<String>("String") { ReadOnlyStringWrapper(it.value) }
column<Int>("Length", String::length)

The first is a lambda that specifies an ObservableValue off your domain object in some form, which in this case is a ReadOnlyStringWrapper. The second uses non-observable fields with getters (and setters if they are mutable) in your domain object (this approach uses reflection).

The cellFormat() function can be called on a TableColumn and accepts a lambda specifying how to render a cell based on the provided value. In this case, the "Length" column will format cells red if the value of the String's length is 4.

.cellFormat {
    if (it == 4) {
        style = "-fx-background-color:#8b0000; -fx-text-fill:white"
            text = it.toString()
    } else {
         text = it.toString()
    }
}

###Example 3 ####A TabPane with tabs containing GridPane layouts as well as a TableView

class MyView : View() {

    override val root = GridPane()

    init {
        with(root) {
            tabpane {
                gridpaneConstraints {
                    vhGrow = Priority.ALWAYS
                }
                tab("Report", HBox()) {
                   label("Report goes here")
                }
                tab("Data", GridPane()) {
                    tableview<String> {
                        gridpaneConstraints {
                            vhGrow = Priority.ALWAYS
                        }
                        items = FXCollections.observableArrayList("Alpha","Beta","Gamma","Delta","Epsilon","Zeta","Eta")
                        column<String>("String") { ReadOnlyStringWrapper(it.value) }
                        column<Int>("Length", String::length )
                                .cellFormat {
                                    if (it ==4) {
                                        style = "-fx-background-color:#8b0000; -fx-text-fill:white"
                                        text = it.toString()
                                    }
                                    else {
                                        text = it.toString()
                                    }
                                }
                    }
                }
            }
        }
    }
}

RENDERED UI

The full list of available functions can be seen in the Builders.kt source file. If you miss a feature, send us a pull request, and we'll be happy to include it.

Next: Async Task Execution

Clone this wiki locally