Kotlin+JavaFXで画面遷移

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

#環境
Kotlin 1.1.2
JDK 1.8.0_131

#作るもの
ボタンがある2つの画面を用意して、ボタンを押すともう片方の画面に遷移するプログラムを作ります。
![画面遷移.png](https://qiita-image-store.s3.amazonaws.com/0/152199/18876f3f-5e6c-7bd7-848e-86b8c2cd4cfe.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。