-
Notifications
You must be signed in to change notification settings - Fork 272
Type Safe Builders
Wiki ▸ Documentation ▸ 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.
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.
###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 their builder equivalents This is helpful if you have Node
components existing outside your builder or are working with Node
items that have no 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
You can build an entire TableView
, complete with a backing list and column value mappings, with a succinct builder structure.
Say you have the given domain object Person
.
class Person(id: Int, name: String, birthday: LocalDate) {
var id by property<Int>()
fun idProperty() = getProperty(Person::id)
var name by property<String>()
fun nameProperty() = getProperty(Person::name)
var birthday by property<LocalDate>()
fun birthdayProperty() = getProperty(Person::birthday)
//assume today is 2016-02-28
val age: Int get() = Period.between(birthday, LocalDate.now()).years
init {
this.id = id
this.name = name
this.birthday = birthday
}
}
You can easily declare a TableView
using a builder.
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<Person> {
items = persons
column("ID",Person::id)
column("Name", Person::name)
column("Birthday", Person::birthday)
column("Age",Person::age)
}
}
}
}
RENDERED UI
Note that the four data properties are in fact JavaFX Properties while the age
property is not. The TableView
builder is smart enough to work with either pattern and handle that abstraction, even accounting for both getters and setters.
You can also specify custom cell formatters quickly by calling cellFormat()
on a TableColumn
. For example, we can highlight cells in red below where the Age is less than 18.
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<Person> {
items = persons
column("ID",Person::id)
column("Name", Person::name)
column("Birthday", Person::birthday)
column("Age",Person::age).cellFormat {
if (it < 18) {
style = "-fx-background-color:#8b0000; -fx-text-fill:white"
text = it.toString()
} else {
text = it.toString()
}
}
}
}
}
}
RENDERED UI
###Example 3 ####A TabPane with tabs containing GridPane layouts as well as a TableView
There are several builders for layouts including GridPane
, BorderPane
, VBox
, and HBox
. There are even builders to set up a TabePane
with multiple Tab
items, each containing a specified Pane
.
This shows how easily we can compose complex layouts with minimal code.
class MyView : View() {
override val root = GridPane()
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) {
tabpane {
gridpaneConstraints {
vhGrow = Priority.ALWAYS
}
tab("Report", HBox()) {
label("Report goes here")
}
tab("Data", GridPane()) {
tableview<Person> {
items = persons
column("ID", Person::id)
column("Name", Person::name)
column("Birthday", Person::birthday)
column("Age", Person::age).cellFormat {
if (it < 18) {
style = "-fx-background-color:#8b0000; -fx-text-fill:white"
text = it.toString()
} else {
text = it.toString()
}
}
}
}
}
}
}
}
RENDERED UI
###Example 4 ####Menu Builders
You can also use the type-safe builders to create menus, including menus inside MenuBar
or ContextMenu
nodes.
class MenuView: View() {
override val root = VBox()
init {
with(root) {
menubar {
menu("File") {
menu("Switch Account") {
menuItem("Facebook") { println("Switching to Facebook") }
menuItem("Twitter") { println("Switching to Twitter") }
}
separator()
menuItem("Save") { println("Saving") }
menuItem("Exit") { println("Exiting")}
}
menu("Edit") {
menuItem("Copy") { println("Copying") }
menuItem("Paste") { println("Pasting") }
separator()
menu("Options") {
menuItem("Account") { println("Launching Account Options") }
menuItem("Security") { println("Launching Security Options") }
menuItem("Appearance") { println("Launching Appearance Options") }
}
}
}
}
}
}
RENDERED UI
You can also specify accelerator and/or graphic node arguments for each menuitem
.
menuitem("Facebook", KeyCombination.valueOf("Shortcut+F"), Icons.Facebook) {
println("Switching to Facebook")
}
Next: Async Task Execution