Edited at

Kotlin+JavaFXで画面遷移

More than 1 year has passed since last update.


はじめに

JavaFXの画面遷移がKotlinで綺麗に書けたので、投稿してみます。


環境

Kotlin 1.1.2

JDK 1.8.0_131


作るもの

ボタンがある2つの画面を用意して、ボタンを押すともう片方の画面に遷移するプログラムを作ります。

画面遷移.png


条件

fxmlファイル名と対応するコントローラーのKotlinのファイル名が同じで、同一パッケージ内に入っていることが前提です。異なるファイル名やパッケージでやりたい場合には、MainAppクラスのreplacePaneを書き換えて下さい。


Application


MainApp.kt

class MainApp : Application() {

private lateinit var stage: Stage // replacePaneで参照する

override fun start(primaryStage: Stage) {
stage = primaryStage
replacePane(Page1())
stage.show()
}

fun replacePane(controller: Any) {
val classPath = controller.javaClass.name
val className = controller.javaClass.simpleName
val loader = FXMLLoader(Class.forName(classPath).getResource("$className.fxml"))
.apply { setController(controller) }
val parent = loader.load<Parent>()
stage.title = className
stage.scene = Scene(parent)

// スマートキャストでtransitionをreplacePaneで初期化できる
if (controller is TransitionPane) {
controller.transition = this::replacePane // 関数参照
}
}
}


MainAppのstageのsceneを入れ替えることで画面遷移を実現しています。画面遷移のために呼び出されるメソッドは、replacePaneで遷移先のコントローラーを引数に取ります。


MainApp.kt

interface TransitionPane {

var transition: ((Any) -> Unit)
}

TransitionPaneは、関数リテラルのプロパティであるtransitionを持つインターフェースです。このインターフェースを、画面遷移させたい(遷移元)コントローラーに実装してあげることで、transitionからreplacePaneを呼べるようにします。replacePaneの中でコントローラーのtransitionがreplacePaneで初期化されています。


MainApp.kt

fun main(args: Array<String>) {

Application.launch(MainApp::class.java, *args)
}

ちなみに、プログラムの起動はこんな感じで行います。


Controller


Page1.kt

class Page1 : Initializable, TransitionPane {

override lateinit var transition: ((Any) -> Unit)
@FXML lateinit var toPage2: Button

override fun initialize(location: URL?, resources: ResourceBundle?) {
toPage2.setOnAction { transition(Page2("Kotlinかわいいよ")) }
}
}


1つ目の画面のコントローラーです。ポイントは、オーバーライドしたtransitionにlateinitをつけることで、MainAppのreplacePaneから初期化できるようにしている所です。


Page2.kt

class Page2(val labelText: String) : Initializable, TransitionPane {

override lateinit var transition: ((Any) -> Unit)
@FXML lateinit var toPage1: Button
@FXML lateinit var label: Label

override fun initialize(location: URL?, resources: ResourceBundle?) {
toPage1.setOnAction { transition(Page1()) }
label.text = labelText
}
}


2つめの画面のコントローラーも同じような感じです。fxmlのfx:controllerでコントローラーを指定するのではなく、FXMLLoaderのsetControllerで指定するようにすることで、コントローラーのコンストラクタに引数を設定できるようになります。


FXML

fxmlはFlowPaneにボタンとかを置いただけです。


Page1.fxml

<FlowPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1" >

<children>
<Button fx:id="toPage2" mnemonicParsing="false" text="Page2へ" />
</children>
</FlowPane>


Page2.fxml

<FlowPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">

<children>
<Button fx:id="toPage1" mnemonicParsing="false" text="Page1へ" />
<Label fx:id="label" text="Label">
<FlowPane.margin>
<Insets left="10.0" />
</FlowPane.margin></Label>
</children>
</FlowPane>


おわりに

画面遷移がコンパクトに分かりやすく書けたかと思います。

それにしてもKotlinは書いていて気持ちがいいですね!Kotlinかわいいよ、Kotlin。