譯自《Components》
組件
JavaFX使用戲劇類比來(lái)組織一個(gè)包含Stage
和Scene
組件的Application
昆码。 TornadoFX通過(guò)提供View
气忠,Controller
和Fragment
組件也構(gòu)建在此類比基礎(chǔ)之上。 雖然TornadoFX也使用Stage
和Scene
赋咽,但View
旧噪,Controller
和Fragment
引入了可以簡(jiǎn)化開發(fā)的新概念。 這些組件多數(shù)被自動(dòng)維護(hù)為單例(singletons)脓匿,并且可以通過(guò)簡(jiǎn)單的依賴注入(dependency injections)和其他方式相互通信淘钟。
您還可以選擇使用FXML,稍后會(huì)討論陪毡。 但首先米母,讓我們繼承App
來(lái)創(chuàng)建用于啟動(dòng)TornadoFX應(yīng)用程序的入口點(diǎn)。
App和View的基礎(chǔ)知識(shí)
要?jiǎng)?chuàng)建TornadoFX應(yīng)用程序毡琉,您必須至少有一個(gè)繼承了App
的類铁瞒。App是應(yīng)用程序的入口點(diǎn),并指定初始View
桅滋。 實(shí)際上它繼承了JavaFX的Application
精拟,但是您不一定需要指定一個(gè)start()
或main()
方法。
但首先虱歪,讓我們繼承App
來(lái)創(chuàng)建自己的實(shí)現(xiàn)蜂绎,并將主視圖(primary view)指定為構(gòu)造函數(shù)的第一個(gè)參數(shù)。
class MyApp: App(MyView::class)
視圖(View)包含顯示邏輯以及節(jié)點(diǎn)(Nodes)的布局笋鄙,類似于JavaFX的Stage
师枣。 它被作為單例(singleton)來(lái)自動(dòng)管理。 當(dāng)您聲明一個(gè)View
萧落,您必須指定一個(gè)root
屬性践美,該屬性可以是任何Node
類型,并且將保存視圖(View)的內(nèi)容找岖。
在同一個(gè)Kotlin文件或在另一個(gè)新文件中陨倡,從View
繼承出來(lái)一個(gè)新類。 覆蓋其抽象root
屬性并賦值VBox
许布,或您選擇的任何其他Node
兴革。
class MyView: View() {
override val root = VBox()
}
但是,我們可能想填充這個(gè)VBox
,作為root
控件杂曲。 使用初始化程序塊 (initializer block)庶艾,讓我們添加一個(gè)JavaFX的Button
和一個(gè)Label
。 您可以使用 “plus assign” +=
運(yùn)算符將子項(xiàng)添加到任何Pane
類型擎勘,包括這里的VBox
咱揍。
class MyView: View() {
override val root = VBox()
init {
root += Button("Press Me")
root += Label("")
}
}
雖然從查看上述代碼來(lái)看,很清楚發(fā)生了什么棚饵,但TornadoFX還提供了一個(gè)構(gòu)建器語(yǔ)法(builder syntax)煤裙,可以進(jìn)一步簡(jiǎn)化您的UI代碼,并可通過(guò)查看代碼來(lái)更輕松地推導(dǎo)出最終的UI噪漾。 我們將逐漸轉(zhuǎn)向構(gòu)建器語(yǔ)法硼砰,最后在下一章中全面介紹構(gòu)建器(builders)。
雖然我們會(huì)向您介紹新概念怪与,但您可能有時(shí)還會(huì)看到?jīng)]有使用最佳做法的代碼。 我們這樣做是為了向您介紹這些概念缅疟,并讓您更深入地了解底層發(fā)生的情況分别。 逐漸地,我們將會(huì)以更好的方式介紹更強(qiáng)大的結(jié)構(gòu)來(lái)解決這個(gè)問(wèn)題存淫。
接下來(lái)我們將看到如何運(yùn)行這個(gè)應(yīng)用程序耘斩。
啟動(dòng)TornadoFX應(yīng)用程序
較新版本的JVM知道如何在沒(méi)有main()
方法的情況下啟動(dòng)JavaFX應(yīng)用程序。 JavaFX應(yīng)用程序(TornadoFX應(yīng)用程序是其擴(kuò)展)桅咆,是繼承javafx.application.Application
的任何類括授。 由于tornadofx.App
繼承了javafx.application.Application
,TornadoFX應(yīng)用程序沒(méi)有什么不同岩饼。 因此荚虚,您將通過(guò)引用com.example.app.MyApp
啟動(dòng)該應(yīng)用程序,并且您不一定需要一個(gè)main()
函數(shù)籍茧,除非您需要提供命令行參數(shù)版述。 在這種情況下,您將需要添加一個(gè)包級(jí)別的主函數(shù)到MyApp.kt文件:
fun main(args: Array<String>) {
Application.launch(MyApp::class.java, *args)
}
這個(gè)主函數(shù)將被編譯進(jìn)com.example.app.MyAppKt
- 注意最后的Kt
寞冯。 當(dāng)您創(chuàng)建包級(jí)別的主函數(shù)時(shí)渴析,它將始終具有完全限定包的類名,加上文件名吮龄,附加Kt
俭茧。
對(duì)于啟動(dòng)和測(cè)試App
,我們將使用Intellij IDEA漓帚。 導(dǎo)航到Run→Edit Configurations (圖3.1)母债。
單擊綠色“+”符號(hào)并創(chuàng)建一個(gè)新的應(yīng)用程序配置(圖3.2)。
指定 “主類(Main class)” 的名稱尝抖,這應(yīng)該是您的App
類场斑。 您還需要指定它所在的模塊(module)漓踢。給配置一個(gè)有意義的名稱,如 “Launcher”漏隐。 之后點(diǎn)擊 “OK”(圖3.3)喧半。
您可以通過(guò)選擇Run→Run 'Launcher'或任何您命名的配置來(lái)運(yùn)行 TornadoFX應(yīng)用程序(圖3.4)。
您現(xiàn)在應(yīng)該看到您的應(yīng)用程序啟動(dòng)了(圖3.5)
恭喜青责! 您已經(jīng)編寫了您的第一個(gè)(雖然簡(jiǎn)單)TornadoFX應(yīng)用程序挺据。 現(xiàn)在看起來(lái)可能不是很好,但是當(dāng)我們涵蓋更多TornadoFX的強(qiáng)大功能時(shí)脖隶,我們將創(chuàng)建大量令人印象深刻的用戶界面扁耐,幾乎沒(méi)有多少代碼,而且只需要很少時(shí)間产阱。 但首先讓我們來(lái)更好地了解App
和View
之間發(fā)生的情況婉称。
了解視圖(View)
讓我們深入了解View
的工作原理以及如何使用它。 看看我們剛剛構(gòu)建的App
和View
類构蹬。
class MyApp: App(MyView::class)
class MyView: View() {
override val root = VBox()
init {
with(root) {
this += Button("Press Me")
this += Label("Waiting")
}
}
}
View
包含JavaFX節(jié)點(diǎn)的層次結(jié)構(gòu)王暗,并在它被調(diào)用的位置通過(guò)名稱注入。 在下一節(jié)中庄敛,我們將學(xué)習(xí)如何利用強(qiáng)大的構(gòu)建器(powerful builders)來(lái)快速創(chuàng)建這些Node
層次結(jié)構(gòu)俗壹。TornadoFX維護(hù)的MyView
只有一個(gè)實(shí)例,有效地使其成為單例藻烤。TornadoFX還支持范圍(scopes)绷雏,它們可以將View
, Fragment
和Controller
的集合組合在一個(gè)單獨(dú)的命名空間中怖亭,如果你愿意的話涎显,那么View
只能是該范圍內(nèi)的單例。 這對(duì)于多文檔接口應(yīng)用程序(Multiple-Document Interface applications)和其他高級(jí)用例非常有用兴猩。 稍后再說(shuō)棺禾。
使用inject()和嵌入視圖(Embedding Views)
您也可以將一個(gè)或多個(gè)視圖注入另一個(gè)View
。 下面我們將TopView
和BottomView
嵌入到MasterView
峭跳。 請(qǐng)注意膘婶,我們使用inject()
代理屬性(delegate property)來(lái)懶惰地注入TopView
和BottomView
實(shí)例。 然后我們調(diào)用每個(gè)child View
的root
來(lái)賦值給BorderPane
(圖3.6)蛀醉。
class MasterView: View() {
val topView: TopView by inject()
val bottomView: BottomView by inject()
override val root = borderpane {
top = topView.root
bottom = bottomView.root
}
}
class TopView: View() {
override val root = label("Top View")
}
class BottomView: View() {
override val root = label("Bottom View")
}
如果您需要在視圖間彼此溝通悬襟,您可以在每個(gè)child View
中創(chuàng)建一個(gè)屬性來(lái)保存parent View
。
class MasterView : View() {
override val root = BorderPane()
val topView: TopView by inject()
val bottomView: BottomView by inject()
init {
with(root) {
top = topView.root
bottom = bottomView.root
}
topView.parent = this
bottomView.parent = this
}
}
class TopView: View() {
override val root = Label("Top View")
lateinit var parent: MasterView
}
class BottomView: View() {
override val root = Label("Bottom View")
lateinit var parent: MasterView
}
更通常地拯刁,您將使用Controller
或ViewModel
在視圖之間進(jìn)行通信脊岳,稍后我們將訪問(wèn)此主題。
使用find()來(lái)注入
inject()
代理(delegate)將懶惰地將一個(gè)給定的組件賦值給一個(gè)屬性。 第一次調(diào)用該組件時(shí)割捅,它將被檢索奶躯。 或者,不使用inject()
代理亿驾,您可以使用find()
函數(shù)來(lái)檢索View
或其他組件的單例實(shí)例嘹黔。
class MasterView : View() {
override val root = BorderPane()
val topView = find(TopView::class)
val bottomView = find(BottomView::class)
init {
with(root) {
top = topView.root
bottom = bottomView.root
}
}
}
class TopView: View() {
override val root = Label("Top View")
}
class BottomView: View() {
override val root = Label("Bottom View")
}
您可以使用find()
或inject()
,但是使用inject()
代理是執(zhí)行依賴注入的首選方法莫瞬。
雖然我們將在下一章更深入地介紹構(gòu)建器(builders)儡蔓,但現(xiàn)在是時(shí)候來(lái)揭示上述示例可以用更加簡(jiǎn)潔明了的語(yǔ)法來(lái)編寫了:
class MasterView : View() {
override val root = borderpane {
top(TopView::class)
bottom(BottomView::class)
}
}
我們不是先注入TopView
和BottomView
,然后將它們各自的root
節(jié)點(diǎn)賦值給BorderPane
的top
和bottom
屬性疼邀,而是使用構(gòu)建器語(yǔ)法(builder syntax喂江,全部小寫)來(lái)指定BorderPane,然后聲明性地告訴TornadoFX拉入兩個(gè)子視圖旁振,并使他們自動(dòng)賦值到top
和bottom
屬性获询。 我們希望您會(huì)認(rèn)同,這是很具表現(xiàn)力的拐袜,具有少得多的樣板(boiler plate)吉嚣。 這是TornadoFX試圖以此為生的最重要的原則之一:減少樣板(boiler plate),提高可讀性阻肿。 最終的結(jié)果往往是更少的代碼和更少的錯(cuò)誤瓦戚。
控制器(Controllers)
在許多情況下沮尿,將UI分為三個(gè)不同的部分被認(rèn)為是一種很好的做法:
- 模型(Model) - 擁有核心邏輯和數(shù)據(jù)的業(yè)務(wù)代碼層丛塌。
- 視圖(View)- 具有各種輸入和輸出控件的視覺(jué)顯示。
- 控制器(Controller) - “中間人(middleman)” 介入(mediating)模型和視圖之間的事件畜疾。
還有其他的MVC流派赴邻,例如MVVM和MVP,所有這些都可以在TornadoFX中使用啡捶。
盡管您可以將模型和控制器的所有邏輯放在視圖之中姥敛,但是最好將這三個(gè)部分清楚地分開,以便最大程度地實(shí)現(xiàn)可重用性瞎暑。 一個(gè)常用的模式是MVC模式彤敛。 在TornadoFX中,可以注入一個(gè)Controller
來(lái)支持View
了赌。
這里給出一個(gè)簡(jiǎn)單的例子墨榄。 使用一個(gè)TextField
創(chuàng)建一個(gè)簡(jiǎn)單的View
,當(dāng)一個(gè)Button
被點(diǎn)擊時(shí)勿她,其值被寫入到一個(gè)“數(shù)據(jù)庫(kù)”袄秩。 我們可以注入一個(gè)處理與寫入數(shù)據(jù)庫(kù)的模型交互的Controller
。 由于這個(gè)例子是簡(jiǎn)化的,所以不會(huì)有實(shí)際的數(shù)據(jù)庫(kù)之剧,但打印的消息將作為占位符(圖3.7)郭卫。
class MyView : View() {
val controller: MyController by inject()
var inputField: TextField by singleAssign()
override val root = vbox {
label("Input")
inputField = textfield()
button("Commit") {
action {
controller.writeToDb(inputField.text)
inputField.clear()
}
}
}
}
class MyController: Controller() {
fun writeToDb(inputValue: String) {
println("Writing $inputValue to database!")
}
}
當(dāng)我們構(gòu)建UI時(shí),我們確保添加對(duì)inputField
的引用背稼,以便以后可以在“Commit”按鈕的onClick
事件處理程序中引用贰军。 當(dāng)單擊“Commit”按鈕時(shí),您將看到控制器向控制臺(tái)打印一行雇庙。
Writing Alpha to database!
重要的是要注意谓形,雖然上述代碼是可工作的,甚至可能看起來(lái)也不錯(cuò)疆前,但是很好的做法是要避免直接引用其他UI元素寒跳。 如果您將UI元素綁定到屬性并操作屬性,那么您的代碼將更容易重構(gòu)竹椒。 稍后我們將介紹ViewModel
童太,它提供了更簡(jiǎn)單的方法來(lái)處理這種類型的交互。
長(zhǎng)時(shí)間運(yùn)行的任務(wù)
每當(dāng)您在控制器中調(diào)用函數(shù)時(shí)胸完,需要確定該函數(shù)是否立即返回书释,或者執(zhí)行潛在的長(zhǎng)時(shí)間運(yùn)行的任務(wù)。 如果您在JavaFX應(yīng)用程序線程中調(diào)用函數(shù)赊窥,則UI將在響應(yīng)完成之前無(wú)響應(yīng)爆惧。 無(wú)響應(yīng)的UI是用戶感知(user perception)的殺手,因此請(qǐng)確保您在后臺(tái)運(yùn)行昂貴的操作锨能。 TornadoFX提供了runAsync
功能來(lái)幫助您扯再。
放置在一個(gè)runAsync
塊內(nèi)的代碼將在后臺(tái)運(yùn)行。 如果后臺(tái)調(diào)用的結(jié)果需要更新您的UI址遇,則必須確保您在JavaFX的應(yīng)用程序線程中應(yīng)用更改熄阻。ui
區(qū)塊正是這樣。
val textfield = textfield()
button("Update text") {
action {
runAsync {
myController.loadText()
} ui { loadedText ->
textfield.text = loadedText
}
}
}
當(dāng)單擊按鈕時(shí)倔约,將運(yùn)行action
構(gòu)建器(將ActionEvent
代理給setAction
方法)中的操作秃殉。 它調(diào)用myController.loadText()
,并當(dāng)它返回shi將結(jié)果應(yīng)用于textfield
的text
屬性浸剩。 當(dāng)控制器功能運(yùn)行時(shí)钾军,UI保持響應(yīng)。
在表面以下绢要, runAsync
會(huì)創(chuàng)建一個(gè)JavaFX的Task
對(duì)象吏恭,并將創(chuàng)建一個(gè)單獨(dú)的線程以在Task
里運(yùn)行你的調(diào)用。 您可以將此Task
賦值給變量袖扛,并將其綁定到UI砸泛,以在運(yùn)行時(shí)顯示進(jìn)度十籍。
事實(shí)上,這是很常見(jiàn)的唇礁,為此還有一個(gè)名為TaskStatus
的默認(rèn)ViewModel
扎谎,它包含running
炎功,message
筒饰,title
和progress
等可觀察值晋柱。 您可以使用TaskStatus
對(duì)象的特定實(shí)例來(lái)提供runAsync
調(diào)用,或使用默認(rèn)值琢融。
TornadoFX源代碼在AsyncProgressApp.kt
文件中包含一個(gè)示例用法界牡。
還有一個(gè)名為runAsyncWithProgress
的runAsync
版本, runAsync
在長(zhǎng)時(shí)間運(yùn)行的操作運(yùn)行時(shí)漾抬,以進(jìn)度指示器來(lái)覆蓋當(dāng)前節(jié)點(diǎn)宿亡。
singleAssign()屬性代理
在上面的例子中,我們用singleAssign
代理初始化了inputField
屬性纳令。 如果要保證只賦值一次值挽荠,可以使用singleAssign()
代理代替Kotlin的lateinit
關(guān)鍵字。 這將導(dǎo)致第二個(gè)賦值引發(fā)錯(cuò)誤平绩,并且在賦值之前過(guò)早訪問(wèn)時(shí)也會(huì)出錯(cuò)圈匆。
您可以在附錄A1中詳細(xì)查看有關(guān)singleAssign()的更多信息,但是現(xiàn)在知道它保證只能賦值一次給var捏雌。 它也是線程安全的跃赚,有助于減輕可變性(mutability)問(wèn)題。
您還可以使用控制器向View提供數(shù)據(jù)(圖3.8)性湿。
class MyView : View() {
val controller: MyController by inject()
override val root = vbox {
label("My items")
listview(controller.values)
}
}
class MyController: Controller() {
val values = FXCollections.observableArrayList("Alpha","Beta","Gamma","Delta")
}
VBox
包含一個(gè)Label
和一個(gè)ListView
纬傲,Controller
的values
屬性被賦值給ListView
的items
屬性。
無(wú)論他們是讀數(shù)據(jù)還是寫數(shù)據(jù)窘奏,控制器都可能會(huì)執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)嘹锁,從而不能在JavaFX線程上執(zhí)行任務(wù)葫录。 本章后面您將學(xué)習(xí)如何使用runAsync
構(gòu)造來(lái)輕松地將工作卸載到工作線程着裹。
分段(Fragment)
您創(chuàng)建的任何View
都是單例,這意味著您通常只能在一個(gè)地方一次使用它米同。 原因是在JavaFX應(yīng)用程序中View
的根節(jié)點(diǎn)(root node)只能具有單個(gè)父級(jí)骇扇。 如果你賦值另一個(gè)父級(jí),它將從它的先前的父級(jí)消失面粮。
但是少孝,如果您想創(chuàng)建一個(gè)短暫(short-lived)的UI,或者可以在多個(gè)地方使用熬苍,請(qǐng)考慮使用Fragment
稍走。 片段(Fragment)是可以有多個(gè)實(shí)例的特殊類型的View
袁翁。 它們對(duì)于彈出窗口或更大的UI甚至是單個(gè)ListCell
都特別有用。 稍后我們將會(huì)看到一個(gè)名為ListCellFragment
的專門的片段婿脸。
View
和Fragment
支持openModal()
粱胜, openWindow()
和openInternalWindow()
,它將在單獨(dú)的窗口(Window)中打開根節(jié)點(diǎn)狐树。
class MyView : View() {
override val root = vbox {
button("Press Me") {
action {
find(MyFragment::class).openModal(stageStyle = StageStyle.UTILITY)
}
}
}
}
class MyFragment: Fragment() {
override val root = label("This is a popup")
}
您也可以將可選參數(shù)傳遞給openModal()
以修改其一些行為焙压。
openModal()
的可選參數(shù)
參數(shù) | 類型 | 描述 |
---|---|---|
stageStyle | StageStyle | 定義·Stage·可能的枚舉樣式之一。 默認(rèn)值: ·StageStyle.DECORATED· |
modality | Modality | 定義Stage一個(gè)可能的枚舉模式類型抑钟。 默認(rèn)值: Modality.APPLICATION_MODAL |
escapeClosesWindow | Boolean | 設(shè)置ESC鍵調(diào)用closeModal() 涯曲。 默認(rèn)值: true |
owner | Window | 指定此階段的所有者窗口 |
block | Boolean | 阻止UI執(zhí)行,直到窗口關(guān)閉在塔。 默認(rèn)值: false |
InternalWindow
盡管openModal
在一個(gè)新的Stage
打開幻件, openInternalWindow
卻在當(dāng)前的根節(jié)點(diǎn)(current root node)或任何你指定的其他節(jié)點(diǎn)上打開:
button("Open editor") {
action {
openInternalWindow(Editor::class)
}
}
內(nèi)部窗口(internal window)的一個(gè)很好的用例是單舞臺(tái)(single stage)環(huán)境(如JPro),或者如果要自定義窗口蛔溃,修剪該窗口使其看起來(lái)更符合你的應(yīng)用程序的設(shè)計(jì)傲武。 內(nèi)部窗口(Internal Window)可以使用CSS
樣式。 有關(guān)樣式可更改(styleable)屬性的更多信息城榛,請(qǐng)查看InternalWindow.Styles
類揪利。
內(nèi)部窗口(internal window)API在一個(gè)重要方面與模態(tài)/窗口(modal/window)不同。 由于窗口(window)在現(xiàn)有節(jié)點(diǎn)上打開狠持,您通常會(huì)在你想要其在上打開的View
中調(diào)用openInternalWindow()
疟位。 您提供要顯示的視圖(View),您也可以選擇通過(guò)owner
參數(shù)提供要在其上打開的節(jié)點(diǎn)(node)喘垂。
openInternalWindow()
的可選參數(shù)
參數(shù) | 類型 | 描述 |
---|---|---|
view | UIComponent | 組件將是新窗口的內(nèi)容 |
view | KClass | 或者甜刻,您可以提供視圖的類而不是實(shí)例 |
icon | Node | 可選的窗口圖標(biāo) |
scope | Scope | 如果指定視圖類,則還可以指定用于獲取視圖的作用域 |
modal | Boolean | 定義在內(nèi)部窗口處于活動(dòng)狀態(tài)時(shí)是否應(yīng)該禁用被覆蓋節(jié)點(diǎn)正勒。 默認(rèn)值: true |
escapeClosesWindow | Boolean | 設(shè)置ESC鍵調(diào)用close() 得院。 默認(rèn)值: true |
owner | Node | 指定此窗口的所有者節(jié)點(diǎn)。 默認(rèn)情況下章贞,該窗口將覆蓋此視圖的根節(jié)點(diǎn) |
關(guān)閉模式窗口
使用openModal()
祥绞, openWindow()
或openInternalWindow()
打開的任何Component
都可以通過(guò)調(diào)用closeModal()
關(guān)閉。 如果需要使用findParentOfType(InternalWindow::class)
也可以直接訪問(wèn)InternalWindow
實(shí)例鸭限。
更換視圖和對(duì)接事件(Replacing Views and Docking Events)
使用TornadoFX蜕径,可以使用replaceWith()
方便地與當(dāng)前View
進(jìn)行交換,并可選擇添加一個(gè)轉(zhuǎn)換(transition)败京。 在下面的示例中兜喻,每個(gè)View
上的Button
將切換到另一個(gè)視圖,可以是MyView1
或MyView2
(圖3.10)赡麦。
class MyView1: View() {
override val root = vbox {
button("Go to MyView2") {
action {
replaceWith(MyView2::class)
}
}
}
}
class MyView2: View() {
override val root = vbox {
button("Go to MyView1") {
action {
replaceWith(MyView1::class)
}
}
}
}
您還可以選擇為兩個(gè)視圖之間的轉(zhuǎn)換指定一個(gè)精巧的動(dòng)畫朴皆。
replaceWith(MyView1::class, ViewTransition.Slide(0.3.seconds, Direction.LEFT)
這可以通過(guò)用另一個(gè)View
的root
替換給定View
上的root
帕识。 View
具有兩個(gè)函數(shù)可以重載(override),用于在其root Node
連接到父級(jí)( onDock()
)以及斷開連接( onUndock()
)時(shí)遂铡。 每當(dāng)View
進(jìn)入或退出時(shí)渡冻,您可以利用這兩個(gè)事件進(jìn)行“連接”和“清理”。 運(yùn)行下面的代碼時(shí)您會(huì)注意到忧便,每當(dāng)View
被交換時(shí)族吻,它將取消(undock )上一個(gè)View
并停靠(dock )新的珠增。 您可以利用這兩個(gè)事件來(lái)管理初始化(initialization)和處理(disposal)任務(wù)超歌。
class MyView1: View() {
override val root = vbox {
button("Go to MyView2") {
action {
replaceWith(MyView2::class)
}
}
}
override fun onDock() {
println("Docking MyView1!")
}
override fun onUndock() {
println("Undocking MyView1!")
}
}
class MyView2: View() {
override val root = vbox {
button("Go to MyView1") {
action {
replaceWith(MyView1::class)
}
}
}
override fun onDock() {
println("Docking MyView2!")
}
override fun onUndock() {
println("Undocking MyView2!")
}
}
將參數(shù)傳遞給視圖
在視圖之間傳遞信息的最佳方式通常是注入ViewModel
。 即使如此蒂教,可以將參數(shù)傳遞給其他組件仍然是很便利的巍举。 find()
和inject()
函數(shù)支持Pair<String, Any>
這樣的varargs
,就可以用于此目的凝垛。 考慮在一個(gè)客戶列表中懊悯,為選定的客戶項(xiàng)打開客戶信息編輯器的情形。 編輯客戶信息的操作可能如下所示:
fun editCustomer(customer: Customer) {
find<CustomerEditor>(mapOf(CustomerEditor::customer to customer).openWindow())
}
這些參數(shù)作為映射傳遞梦皮,其中鍵(key)是視圖中的屬性(property)炭分,值(value)是您希望的屬性的任何值。 這為您提供了一種配置目標(biāo)視圖參數(shù)的安全方式剑肯。
這里我們使用Kotlin的to
語(yǔ)法來(lái)創(chuàng)建參數(shù)捧毛。 如果你愿意,這也可以寫成Pair(CustomerEditor::customer, customer)
让网。 編輯器現(xiàn)在可以這樣訪問(wèn)參數(shù):
class CustomerEditor : Fragment() {
val customer: Customer by param()
}
如果要檢查參數(shù)呀忧,而不是盲目依賴它們是可用的,您可以將其聲明為可空(nullable)溃睹,或參考其params
映射:
class CustomerEditor : Fragment() {
init {
val customer = params["customer"] as? Customer
if (customer != null) {
...
}
}
}
如果您不關(guān)心類型安全性而账,還可以將參數(shù)作為mapOf("customer" to customer)
傳遞,但是如果在目標(biāo)視圖中重命名屬性因篇,則會(huì)錯(cuò)過(guò)自動(dòng)重構(gòu)(automatic refactoring)泞辐。
訪問(wèn)主舞臺(tái)(primary stage)
View
具有一個(gè)名為primaryStage
的屬性,允許您操作支持它的Stage
的屬性惜犀,例如窗口大小铛碑。 通過(guò)openModal()
打開的任何View
或Fragment
也將有一個(gè)modalStage
屬性可用狠裹。
訪問(wèn)場(chǎng)景(scene)
有時(shí)需要從View
或Fragment
獲取當(dāng)前場(chǎng)景虽界。 這可以通過(guò)root.scene
來(lái)實(shí)現(xiàn),或者如果你位于一個(gè)類型安全的構(gòu)建器(type safe builder)內(nèi)部涛菠,還有一個(gè)更短的方法莉御,只需使用scene
撇吞。
訪問(wèn)資源(resources)
許多JavaFX API將資源作為URL
或URL
的toExternalForm
。 要檢索資源url礁叔,通常會(huì)如下所寫:
val myAudioClip = AudioClip(MyView::class.java.getResource("mysound.wav").toExternalForm())
每個(gè)Component
都有一個(gè)resources
對(duì)象牍颈,可以檢索resources
的外部形式url(external form url),如下所示:
val myAudiClip = AudioClip(resources["mysound.wav"])
如果您需要一個(gè)實(shí)際的URL
琅关,可以這樣檢索:
val myResourceURL = resources.url( "mysound.wav" )
resources
助手還有一些其他有用的功能煮岁,可幫助您將相對(duì)于Component
的文件轉(zhuǎn)換為所需類型的對(duì)象:
val myJsonObject = resources.json("myobject.json")
val myJsonArray = resources.jsonArray("myarray.json")
val myStream = resources.stream("somefile")
值得一提的是, json
和jsonArray
函數(shù)也可以在InputStream
對(duì)象上使用涣易。
資源與Component
相對(duì)應(yīng)画机,但您也可以通過(guò)完整路徑,從/
開始檢索資源新症。
動(dòng)作的快捷鍵和組合鍵
您可以在鍵入某些組合鍵時(shí)觸發(fā)動(dòng)作(fire actions)步氏。 這是用shortcut
函數(shù)完成的:
shortcut(KeyCombination.valueOf("Ctrl+Y")) {
doSomething()
}
還有一個(gè)字符串版本的shortcut
函數(shù)與此相同,但是不太冗長(zhǎng):
shortcut("Ctrl+Y")) {
doSomething()
}
您還可以直接向按鈕操作添加快捷方式:
button("Save") {
action { doSave() }
shortcut("Ctrl+S")
}
觸摸支持
JavaFX對(duì)觸摸的支持開箱即用徒爹,現(xiàn)在唯一需要改進(jìn)的地方就是以更方便的方式處理shortpress
和longpress
荚醒。 它由兩個(gè)類似于action
的函數(shù)組成,可以在任何Node
上進(jìn)行配置:
shortpress { println("Activated on short press") }
longpress { println("Activated on long press") }
這兩個(gè)函數(shù)都接受consume
參數(shù)隆嗅,默認(rèn)情況下為false
界阁。 將其設(shè)置為true
將防止按壓事件(press event)發(fā)生事件冒泡(event bubbling)。longpress
函數(shù)還支持一個(gè)threshold
參數(shù)胖喳,用于確定longpress
積累的時(shí)間铺董。 默認(rèn)為700.millis
。
總結(jié)
TornadoFX充滿了簡(jiǎn)單禀晓,直觀而又強(qiáng)大的注入工具來(lái)管理視圖和控制器(Views and Controllers)精续。 它還使用Fragment
簡(jiǎn)化對(duì)話框和其他小型UI。 盡管迄今為止粹懒,我們構(gòu)建的應(yīng)用程序非常簡(jiǎn)單重付,但希望您能欣賞到TornadoFX給JavaFX引入的簡(jiǎn)化概念。 在下一章中凫乖,我們將介紹可以說(shuō)是TornadoFX最強(qiáng)大的功能:Type-Safe Builders
确垫。