TornadoFX編程指南,第4章,基本控件

譯自《Basic Controls

基本控件

TornadoFX最令人興奮的功能之一就是Type-Safe Builders。 配置(Configuring)和布置(laying out)復雜UI的控件可能是冗長而困難的,代碼可能很快變得混亂而難以維護。 幸運的是,您可以使用由Groovy開創(chuàng)的強大的閉包模式(powerful closure pattern) ,以純粹和簡單的Kotlin代碼來創(chuàng)建結構化的UI布局。

雖然我們稍后會學習如何應用FXML,但是您可能會發(fā)現(xiàn)構建器(builders)是一個表達力強勁的方法,可以在一小段時間內創(chuàng)建復雜的UI。 沒有配置文件或編譯器的魔術,構建器使用純Kotlin代碼完成。 接下來的幾個章節(jié)將把構建器分為不同類別的控件。 一路上,您將逐漸通過將這些構建器集成在一起來構建更復雜的UI。

但首先,讓我們來看看構建器如何實際工作。

構建器如何工作

Kotlin的標準庫提供了一些有用的“塊(block)”函數(shù),目標是任何類型T。 有with()函數(shù)(with() function) ,它允許你編寫一個item的代碼,好像你正好在它的類中一樣。

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            this += Button("Press Me")
        }
    }
}

在上面的例子中,with()函數(shù)接受root作為參數(shù)。 以下的閉包參數(shù)通過將root引用為this來直接操作root,這被安全地解釋為VBox 。 通過調用它的plusAssign()擴展運算符(extended operator)將一個Button添加到VBox 。

或者,Kotlin中的每個類型都有一個apply()函數(shù)(apply() function) 。 這與with()是幾乎相同的功能,但它實際上是一個擴展的高階函數(shù)(extended higher-order function)。

class MyView : View() {

    override val root = VBox()

    init {
        root.apply {
            this += Button("Press Me")
        }
    }
}

with()apply()完成類似的任務。 他們安全地解釋他們所針對的類型,并允許對其進行操作。 但是, with()返回lambda中的最后一個語句,而apply()實際上返回了它所針對的項目。 因此,如果您在Button上調用apply()來操作,例如其字體顏色和動作,那么Button返回其自己是很有幫助的,以免破壞聲明流程(declaration flow)。

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            this += Button("Press Me").apply {
                textFill = Color.RED
                action { println("Button pressed!") }
            }
        }
    }
}

上面表達了構建器工作的基本概念,并且正在進行三項任務:

  1. 創(chuàng)建一個Button
  2. Button被修改
  3. Button被添加到它的“父級(parent)”,它是一個VBox

當聲明任何Node,這三個步驟是如此常見,以至于TornadoFX使用策略性放置的擴展函數(shù)(strategically placed extension functions)來簡化它們,如下所示的button()。

class MyView : View() {

    override val root = VBox()

    init {
        with(root) {
            button("Press Me") {
                textFill = Color.RED
                action { println("Button pressed!") }
            }
        }
    }
}

雖然這看起來更干凈,但您可能會想:“我們是如何擺脫this +=apply()函數(shù)調用的呢?為什么我們使用一個名為button()的函數(shù)而不是實際的Button呢? 我們不會太深入如何做到這一點,如果你好奇,你可以隨時挖掘源代碼(source code ) 。

但本質上, VBox (或任何可定位的組件)具有稱為button()的擴展函數(shù)。 它接受一個文本參數(shù)和一個可選的閉包,目標是它將實例化的Button。 當調用此函數(shù)時,將創(chuàng)建一個帶指定文本的Button,對其應用閉包,將其添加到在其上調用的VBox ,然后將其返回。

為進一步提高效率,您可以重載(override )Viewroot ,并為其賦值一個構建器函數(shù)(builder function),從而可以避免需要任何init()with()塊。

class MyView : View() {

    override val root = vbox {
        button("Press Me") {
            textFill = Color.RED
            action { println("Button pressed!") }
        }
    }
}

當您將控件嵌套到其他控件中時,構建器模式變得特別強大。 使用這些構建器擴展函數(shù),您可以輕松地將多個HBox實例嵌入到一個VBox ,并創(chuàng)建一個結構清晰的UI代碼(圖4.1)。

class MyView : View() {

    override val root = vbox {
         hbox {
             label("First Name")
             textfield()
         }
         hbox {
             label("Last Name")
             textfield()
         }
         button("LOGIN") {
             useMaxWidth = true
         }
    }
}
圖4.1

另外請注意,我們將在稍后了解TornadoFX的專有Form,這將使像這樣的簡單輸入UI的代碼更簡單。

如果需要保存對TextField等控件的引用,則可以將它們保存到變量或屬性中,因為函數(shù)返回生成的控件。 建議您使用singleAssign()代理來確保屬性只賦值一次。

class MyView : View() {

    var firstNameField: TextField by singleAssign()
    var lastNameField: TextField by singleAssign()

    override val root = vbox {
        hbox {
            label("First Name")
            firstNameField = textfield()
        }
        hbox {
            label("Last Name")
            lastNameField = textfield()
        }
        button("LOGIN") {
            useMaxWidth = true
            action {
                println("Logging in as ${firstNameField.text} ${lastNameField.text}")
            }
        }
    }

}

請注意,非構建器擴展函數(shù)和屬性也已添加到不同的控件中。useMaxWidthNode的擴展屬性,它將Node設置為占用允許的最大寬度。 在接下來的幾章中,我們將會看到更多這些有用的擴展。

在接下來的章節(jié)中,我們將介紹每個JavaFX控件的每個相應的構建器。 利用上述理念,您可以從頭到尾或者作為參考來閱讀以后的章節(jié)。

基本控件的構建器

本章的其余部分將介紹常見的JavaFX控件(如Button, LabelTextField構建器。 下一章將介紹數(shù)據(jù)驅動控件(如ListView, TableViewTreeTableView構建器)。

Button

對于任何Pane,您可以調用其button()擴展函數(shù)向其添加一個Button。 您可以選擇傳遞text參數(shù)和Button.() -> Unitlambda來修改其屬性。

Pane中,這將添加一個帶有紅色文本的Button,并在每次點擊時打印 “Button pressed!” (圖4.2)

button("Press Me") {
    textFill = Color.RED
    action {
        println("Button pressed!")
    }
}
圖4.2

Label

您可以調用label()擴展函數(shù)將Label添加到給定的Pane。 或者,您可以提供一個文本(StringProperty<String>),一個圖形 (類型為NodeObjectProperty<Node>)和Label.() -> Unit的lambda來修改其屬性(圖4.3)。

label("Lorem ipsum", circle(10, 10, 5)) {
    textFill = Color.BLUE
}
圖4.3

TextField

對于任何Pane,您可以通過調用textfield()擴展函數(shù)來添加一個TextField(圖4.4)。

textfield()
圖4.4

您可以選擇提供初始文本(initial text)以及閉包(closure)以操縱TextField。 例如,我們可以在其textProperty()添加一個監(jiān)聽器,并在每次更改時打印其值(圖4.5)。

textfield("Input something") {
    textProperty().addListener { obs, old, new ->
        println("You typed: " + new)
    }
}
圖4.6

PasswordField

如果您需要一個TextField來獲取敏感信息,可能需要考慮使用PasswordField。 它將顯示匿名字符以防止窺視。 您還可以提供初始密碼作為參數(shù),以及代碼塊來操作它(圖4.7)。

passwordfield("my_password") {
    requestFocus()
}
圖4.7

CheckBox

您可以創(chuàng)建一個CheckBox以快速創(chuàng)建一個真/假(true/false)狀態(tài)控件,并可選擇使用塊來操作它(圖4.8)。

checkbox("Admin Mode") {
    action { println(isSelected) }
}

請注意,動作塊( action block)被包含在checkbox內,從而您可以訪問它的isSelected屬性。 如果您不需要訪問CheckBox的屬性,您可以這樣寫:checkbox("Admin Mode").action {} 。

圖4.9

您還可以提供一個Property<Boolean>,這將會綁定到其選擇狀態(tài) 。

val booleanProperty = SimpleBooleanProperty()

checkbox("Admin Mode", booleanProperty).action { println(isSelected) }

ComboBox

ComboBox是一個下拉式控件,允許從中選擇一組固定的值(圖4.10)。

val texasCities = FXCollections.observableArrayList("Austin",
    "Dallas","Midland", "San Antonio","Fort Worth")

combobox<String> {
    items = texasCities
}
圖4.10

如果將values聲明為參數(shù),則不需要指定通用類型(generic type)。

val texasCities = FXCollections.observableArrayList("Austin",
        "Dallas","Midland","San Antonio","Fort Worth")

combobox(values = texasCities)

您還可以指定要綁定到所選值的Property<T>

val texasCities = FXCollections.observableArrayList("Austin",
        "Dallas","Midland","San Antonio","Fort Worth")

val selectedCity = SimpleStringProperty()

combobox(selectedCity, texasCities)

ToggleButton

ToggleButton是一個按照它的選擇狀態(tài)來表示真/假(true/false)狀態(tài)的按鈕(圖4.11)。

togglebutton("OFF") {
    action {
        text = if (isSelected) "ON" else "OFF" 
    }
}

也許一個控制按鈕文本的更加自然的方式(idomatic way)是使用綁定到textPropertyStringBinding:

togglebutton {
    val stateText = selectedProperty().stringBinding {
        if (it == true) "ON" else "OFF"
    }
    textProperty().bind(stateText)
}
圖4.11

您可以選擇將ToggleGroup傳遞給togglebutton()函數(shù)。 這將確保ToggleGroup所有ToggleButton只能一次選擇一個(圖4.12)。

class MyView : View() {

    private val toggleGroup = ToggleGroup()

    override val root = hbox {
            togglebutton("YES", toggleGroup)
            togglebutton("NO", toggleGroup)
            togglebutton("MAYBE", toggleGroup)
    }
}
圖4.12

RadioButton

RadioButtonToggleButton具有相同的功能,但具有不同的視覺風格。 當它被選中時,它會填充一個環(huán)形控件(circular control)(圖4.13)。

radiobutton("Power User Mode") {
    action {
        println("Power User Mode: $isSelected")
    }
}
圖4.13

也可以像ToggleButton 一樣,將RadioButton設置為包含在ToggleGroup內,以便一次只能選擇該組中的一個項目(圖4.14)。

class MyView : View() {

    private val toggleGroup = ToggleGroup()

    override val root = vbox {
            radiobutton("Employee", toggleGroup)
            radiobutton("Contractor", toggleGroup)
            radiobutton("Intern", toggleGroup)
    }
}
圖4.14

DatePicker

DatePicker聲明起來是很簡單的。 它允許您從彈出的日歷控件(popout calendar control)中選擇日期。 您可以選擇提供一個塊來操作它(圖4.15)。

datepicker {
    value = LocalDate.now()
}
圖4.15

您還可以提供Property<LocalDate>作為綁定到其值的參數(shù)。

val dateProperty = SimpleObjectProperty<LocalDate>()

datepicker(dateProperty) {
    value = LocalDate.now()
}

TextArea

TextArea允許您輸入多行自由格式文本(multiline freeform text)。 聲明時,您可以選擇提供初始文本value以及處理程序塊(圖4.16)。

textarea("Type memo here") {
    selectAll()
}
圖4.16

ProgressBar

ProgressBar可視化完成一個過程趨近完成的進度。 您可以選擇提供小于或等于1.0的初始Double值,表示完成百分比(圖4.17)。

progressbar(0.5)
圖4.17

這是一個更加動態(tài)的例子,模擬一個過程在短時間內的進展。

progressbar() {
    thread {
        for (i in 1..100) {
            Platform.runLater { progress = i.toDouble() / 100.0 }
            Thread.sleep(100)
        }
    }
}

您還可以傳遞一個將progress綁定到其值的Property<Double>,以及一個操作ProgressBar的塊。

progressbar(completion) {
    progressProperty().addListener {
        obsVal, old, new ->  print("VALUE: $new")
    }
}

ProgressIndicator

ProgressIndicator在功能上與ProgressBar相同,但使用填充圓(filling circle)而不是進度條(圖4.18)。

progressindicator {
    thread {
        for (i in 1..100) {
            Platform.runLater { progress = i.toDouble() / 100.0 }
            Thread.sleep(100)
        }
    }
}
圖4.18

就像ProgressBar一樣,您可以提供一個Property<Double>,和/或一個塊作為可選參數(shù)(圖4.19)。

val completion = SimpleObjectProperty(0.0)
progressindicator(completion)

ImageView

您可以使用imageview()嵌入圖像。

imageview("tornado.jpg")
圖4.19

像大多數(shù)其他控件一樣,您可以使用塊來修改其屬性(圖4.20)。

imageview("tornado.jpg") {
    scaleX = .50
    scaleY = .50
}
圖4.20

ScrollPane

您可以將控件嵌入到ScrollPane,使其可滾動。 當可用區(qū)域變得小于控件時,滾動條將顯示出來,以導航該控件的區(qū)域。

例如,您可以在ScrollPane包裝一個ImageView(圖4.21)。

scrollpane {
    imageview("tornado.jpg")
}
圖4.21

請記住,許多控件(如TableViewTreeTableView已經有滾動條,因此將它們包裝在ScrollPane是不必要的(圖4.22)。

Hyperlink

您可以創(chuàng)建Hyperlink控件來模擬典型的到文件,網站的超鏈接的行為,或者簡單地執(zhí)行操作。

hyperlink("Open File").action { println("Opening file...") }
圖4.22

Text

您可以使用格式化的屬性(formatted properties)添加一個簡單的Text。 該控件比Label簡單且原始(simpler and rawer),并且可以使用\n字符分隔段落(圖4.23)。

text("Veni\nVidi\nVici") {
    fill = Color.PURPLE
    font = Font(20.0)
}
圖4.23

TextFlow

如果您需要連接使用不同格式的多條文本,則TextFlow控件可能會有所幫助(圖4.24)。

textflow {
    text("Tornado") {
        fill = Color.PURPLE
        font = Font(20.0)
    }
    text("FX") {
        fill = Color.ORANGE
        font = Font(28.0)
    }
}
圖4.24

您可以使用標準構建器函數(shù),將任何Node添加到textflow,包括圖像。

Tooltips

在任何Node上,您都可以通過tooltip()函數(shù)指定Tooltip`(圖4.25)。

button("Commit") {
    tooltip("Writes input to the database")
}
圖4.25

像大多數(shù)其他構建器一樣,您可以提供一個閉包來自定義Tooltip本身。

button("Commit") {
    tooltip("Writes input to the database") {
        font = Font.font("Verdana")
    }
}

還有許多其他構建器控件,TornadoFX的維護者已經努力為每個JavaFX控件創(chuàng)建一個構建器。 如果您需要不在這里的內容,請使用Google查看是否包含在JavaFX中。 如果JavaFX中有一個控件可用,那么在TornadoFX中就有一個名稱相同的構建器。

總結

在本章中,我們了解到TornadoFX構建器以及它們如何通過使用Kotlin擴展函數(shù)工作。 我們還涵蓋了基本控件的構建器,如Button,TextFieldImageView。 在接下來的章節(jié)中,我們將了解桌面,布局,菜單,圖表和其他控件的構建器。 正如您將看到的,將所有這些構建器結合在一起創(chuàng)建了一種強大的方法,以非常結構化和最小的代碼來表達復雜的UI。

這些并不是TornadoFX API中唯一的控件構建器,本指南盡可能地跟上。 始終檢查GitHub上TornadoFX以查看可用的最新構建器和功能,如果您看到有任何缺失,請?zhí)峤粏栴}。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容