Kotlin
JavaFX

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。