開発チームエンジニアのyasuです。
KotlinでJavaFXを扱う解説記事の第2回になります。
前回記事【KotlinでGUIアプリ開発1】Hello, worldを表示してみようの続きになりますので、
未読の方はそこから読まれることをおすすめします。
前回では、KotlinでJavaFXのコードを書き、Hello, worldを表示するところまでを実際に書いてみました。
今回はもう少し詳しく、JavaFXの説明とKotlinでの書き方について解説していきます。
JavaFXの構造
JavaFXの構造を大きく見ると、Stage、Scene、Paneの3つの要素からGUIが成り立っています。
一番外枠のStageはウィンドウを表すクラスで、Stageインスタンスをshow()することでウィンドウを表示することができます。
Sceneはウィンドウ内に表示する領域をもつクラスです。1つのStageに対して1つのSceneがセットされます。
StageにセットするSceneを切り替えることで画面の要素をまるごと切り替えることができます。
そして、表示する要素を持つクラスがPaneです。前回SceneBuilderで作成したfxmlファイルは、このPaneになります。
Stageは1つのSceneを持ち、Sceneも1つのPaneを持つという親子関係になっています。
Paneの中には、ボタンやメニューバーなどの複数の要素を持たせることが可能です。
このPaneの中に設置するボタンなどの要素をコントロールと言います。
また、Paneの中にさらに複数のPaneを持たせることもできますが、最後にSceneにセットする際には最も親のPaneを指定してあげる必要があります。
以上を踏まえたうえで、前回記事の最後に書いたコードを再度見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import javafx.application.Application import javafx.fxml.FXMLLoader import javafx.scene.Scene import javafx.scene.layout.Pane import javafx.stage.Stage class AppMain : Application() { override fun start(primaryStage: Stage) { primaryStage.title = "KotlinでJavaFX" // FXMLファイルのURLを取得 val fxml = javaClass.getResource("fxml/hello.fxml") // PaneとしてFXMLをロード val pane = FXMLLoader.load(fxml) // シーンにセット val scene = Scene(root) // ステージにセット primaryStage.scene = scene primaryStage.show() } } |
最初にfxmlのURLを取得したら、FXMLLoader#load関数で読み込んでPaneを取得しています。
そして、Paneの親要素になるSceneにセット、そしてそのSceneをメインウィンドウになるStageにセットしています。
最後にStage#show関数を実行することで1つのウィンドウが表示されているという仕組みです。
以上を踏まえると、セットするSceneやPaneを切り替えることで表示要素を変えたりできますし、
別ウィンドウを表示したいときは、Stageを新しく生成してshow()すればいいことが想像できるでしょう。
まさしくその通りに書くことができるはずです。
FXMLの構造
前回、実際にUI部分を定義したのはfxmlファイルでした。
このfxmlファイルはPaneクラスに対応するファイルです。
Paneには色々な種類がありますが、SceneにセットできるPaneは1つだけです。
UIの骨組みとなる要素と考えてみるとわかりやすいでしょうか。
前回、Hello, worldを表示した際に使ったPaneはAnchorPaneでした。
実際に確認してみましょう。
前回作ったhello.fxmlファイルを開いて下図部分を確認してみます。
一番親の要素がPaneのはずです。
そしてAnchorPaneになっていると思います。
AnchorPaneは、LabelやTextField、Buttonなどを自由な位置で配置することのできるPaneです。
実際に、ドラッグ&ドロップでLabelを自由に配置することができたと思います。
では、別のPaneを試してみましょう。
セットされているAnchorPaneを右クリックで削除します。
そしてContainersからBorderPaneを選択して配置してみましょう。
BorderPaneはTOP、LEFT、CENTER、RIGHT、BOTTOMという骨組みが提供されるPaneです。
AnchorPaneのように自由に配置することは難しくなりますが、配置の関係性を保ってデザインをすることができます。
では、今度はControlsからMenuBarを選択してTOP要素に配置してみます。
簡単にUI上部にメニューバーを配置することができました。
このようにして、FXMLファイルを使ってPaneとその子要素を定義することでJavaFXアプリケーションを作成していきます。
JavaとKotlinの違いを探る
JavaでJavaFXを触ったことがある人は、もう既に微妙な違いを感じているかもしれませんが、
ここではいくつかJavaで書く時との違いを見ていくことにしましょう。
getter/setterの違い
1 2 3 4 5 6 7 |
/** ウィンドウのタイトルを設定する */ // Javaの場合 primaryStage.setTitle("JavaでJavaFX"); // Kotlinの場合 primaryStage.title = "KotlinでJavaFX" |
JavaもKotlinもオブジェクト指向言語なので、オブジェクトのプロパティを変更することで状態を変更します。
上記の例のように、Javaでは各プロパティ値を、提供されているsetterおよびgetterを介して設定します。
Kotlinの場合は、setter/getterメソッドではなく、直接プロパティ値に代入処理をしているように見えます。
実際のところは、内部的にアクセサメソッドが提供されているのですが、シンプルに代入処理をおこなうように、
プロパティ値にアクセスすることができます。
JavaFXの場合でも、Kotlinから扱うときはこのように直接プロパティを指定して設定していきます。
他にも、Kotlin向けに表現が変更されているプロパティがあるので見ていきましょう。
1 2 3 4 5 6 7 |
/** Boolean値設定 */ // Javaの場合 stage.setResizable(false); // Kotlinの場合 stage.isResizable = false |
ウィンドウサイズを変更可能かどうかを設定するプロパティです。
Boolean値の設定の場合でも、Kotlinで書く際はsetterを介さずisResizableプロパティへの代入処理を行っています。
他の型の場合でも同様です。
1 2 3 4 5 6 7 |
/** プロパティ値以外の設定メソッド */ // Javaの場合 stage.initModality(Modality.APPLICATION_MODAL); // Kotlinの場合 stage.initModality(Modality.APPLICATION_MODAL) |
プロジェクトへのアクセサメソッド以外の場合はJavaと共通です。
上記の場合は、ウィンドウをモーダルとして表示するときの設定になりますが、
JavaとKotlinは同じ形式になっています。
同様に、プロパティ値へのアクセサメソッド以外のset/getメソッドはKotlinでもJavaと共通です。
アプリケーションのエントリーポイント
JavaとKotlinとで一番異なるのはJavaFXのエントリーポイントの書き方でしょう。
KotlinではJavaFXを継承したクラスと、Mainファイルを2つ用意する必要がありましたが、
JavaではApplicationを継承したクラスがそのままエントリーポイントになります。
※import文を省いております
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * Java */ public class Main extends Application { // Javaの場合start関数がそのままエントリーポイントになる @Override public void start(Stage primaryStage) { primaryStage.show(); } } |
Javaの場合は、継承してオーバーライドしたstart関数がエントリーポイントとして機能します。
1 2 3 4 5 6 7 8 9 10 11 |
/** * Kotlin */ class AppMain : Application() { override fun start(primaryStage: Stage) { primaryStage.show() } } |
1 2 3 4 5 6 7 8 |
/** * JavaFXのエントリーポイントを呼び出すmain関数 */ fun main(args: Array) { Application.launch(AppMain().javaClass, *args) } |
Kotlinの場合は少し手間が増えています。
Applicationクラスの継承実装部分はJavaと同一で問題ありません。
しかし、アプリケーションのエントリーポイントとして、別途main関数を定義する必要が発生しています。
Javaの場合は、main関数はMainクラス内に宣言するルールですが、
Kotlinの場合はmain関数はクラス内に宣言することができません。
そのため、JavaFXのエントリーポイントは、Kotlinの場合そのままでは実行エントリーポイントとして機能することはできず、
別途main関数を宣言して実行する必要があります。
それでは、kotlinのmain関数をもう少し詳しく見ていきましょう
1 2 3 |
Application.launch(AppMain().javaClass, *args) |
JavaFXのApplication#launch関数を呼び出しています。
この関数はJavaFXのスタンドアロンアプリケーションを起動するメソッドです。
実は、Javaで書いた場合でもこのlaunch関数を呼び出すことで明示的に起動することができます。
launch関数の第一引数にはApplicationクラスを継承したクラスを指定します。
そして第二引数にはアプリケーションに渡したいコマンドライン引数を渡します。
簡単なHello, worldを表示する処理を書いた場合のJavaとKotlinの差は実はこれくらいで、
あとは共通の手順で書くことができます。
今後、コントローラークラスを定義した際にも差異がでてきますが 、そのときにまた説明するとしましょう。
まとめ
今回でJavaFXのおおまかな構造をある程度把握できたでしょうか。
次回はボタンをクリックしたときのイベント処理などを扱う、
コントローラークラスの作り方をまとめていく予定です。お楽しみに。