これはJavaFX Advent Calendar 2014の4日目の記事です。
昨日は@boochnichさんのScene Builder 2.0.1をビルドするです。
明日はsoutokuさんのJavaFXでWYSIWYGエディタを作るです。
JavaFXアプリケーションを作るときに、FXMLを用いると比較的見通しがよく、メンテナンスしやすいアプリケーションが作れると思います。
これはSwingなどでアプリケーションを作ったことがある方なら、だいたいわかると思います。
ただ、アプリケーションが大きくなり始めると、FXMLファイルが複雑になって、メンテナンスが難しいものになってしまうのではないかと思います。
というわけで、僕がJavaFXアプリケーションを作るとき(めったに作らないけど…)は、FXMLを極力小さく作るようにしています。
例
ここでは例としてドラクエXでくじ引きした時にカウントを取るのが面倒でつくったアプリを取り上げます。
これは、まあ、見たまんまのアプリで、あたった賞の等級にある「当」ボタンを押すと、テキストフィールドの数値と合計の部分の数値が1増えて、「×」ボタンを押すと数値が減るという単純なものです。
これは各賞の部分が同じ形になっているため、小さいアプリケーションながらも、ボタンの位置をずらすといった変更を行うときに、同じ作業を何度もする必要があるなど、メンテナンスが煩わしいアプリケーションです。
最初作ったときはScene Builderで「×」ボタンなしのものを作ったわけですが、「×」ボタンを追加するときにあまりの煩わしさに、FXMLを小分けするような造りにしました。
コード
このアプリケーションのFXMLは次のような感じになっています。
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.text.*?> <?import javafx.geometry.*?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <fx:root type="javafx.scene.layout.HBox" prefHeight="39.0" prefWidth="280.0" style="-fx-background-color: #cff;" xmlns:fx="http://javafx.com/fxml"> <children> <Label fx:id="label" contentDisplay="CENTER" prefHeight="25.0" prefWidth="80.0" textFill="#003399" textOverrun="CLIP"> <HBox.margin> <Insets bottom="7.0" left="10.0" right="7.0" top="7.0" /> </HBox.margin> </Label> <Label fx:id="amount" alignment="CENTER_RIGHT" prefHeight="25.0" prefWidth="50.0" style="-fx-border-color: #039; -fx-border-radius: 3px;" text="0"> <HBox.margin> <Insets bottom="7.0" left="10.0" right="7.0" top="7.0" /> </HBox.margin> <padding> <Insets right="7.0" /> </padding> </Label> <Button fx:id="plus" onAction="#increment" alignment="CENTER" mnemonicParsing="false" prefHeight="25.0" prefWidth="40.0" style="-fx-background-color: #34f; -fx-font-weight: bolder;" text="当" textFill="WHITE"> <HBox.margin> <Insets bottom="7.0" left="10.0" right="7.0" top="7.0" /> </HBox.margin> <font> <Font name="System Bold" size="13.0" /> </font> </Button> <Button fx:id="minus" onAction="#decrement" alignment="CENTER" mnemonicParsing="false" prefHeight="25.0" prefWidth="40.0" style="-fx-background-color: #f44; -fx-font-weight: bold;" text="×" textFill="WHITE"> <HBox.margin> <Insets bottom="7.0" left="10.0" right="7.0" top="7.0" /> </HBox.margin> </Button> </children> <padding> <Insets bottom="7.0" left="7.0" right="7.0" top="7.0" /> </padding> </fx:root>
これに対するコントローラーは以下のとおりです。
package org.mikeneck.dqx.lottery.control; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import org.mikeneck.dqx.lottery.LotteryException; import org.mikeneck.dqx.lottery.intercept.CountManager; import org.mikeneck.dqx.lottery.model.Rank; import java.io.IOException; public class LotteryResultRow extends HBox { @FXML private Label label; @FXML private Label amount; @FXML private Button plus; @FXML private Button minus; private final CountManager manager; private final Rank rank; public LotteryResultRow(Rank rank, CountManager manager) { this.rank = rank; this.manager = manager; FXMLLoader loader = new FXMLLoader(getClass() .getClassLoader() .getResource(rank.getFxmlFile())); loader.setRoot(this); loader.setController(this); try { loader.load(); } catch (IOException e) { String message = String.format("fxmlファイルのロードに失敗しました。 %s", rank.toString()); throw new LotteryException(message, e); } } public void setLabelText (String text) { label.setText(text); } @FXML public void increment () { int now = manager.increment(rank); updateView(now); manager.updateTotal(); } @FXML public void decrement () { int now = manager.decrement(rank); updateView(now); manager.updateTotal(); } public void updateView () { int now = manager.get(rank); updateView(now); } private void updateView(int now) { amount.setText(String.format("%d", now)); } }
また、この小さな部品を画面の中に組み込むコードは次のように親要素のchildNodes
に追加していきます。
final ObservableList<Node> nodes = vBox.getChildren(); Stream.of(Rank.values()).forEach(rank -> { LotteryResultRow row = new LotteryResultRow(rank, countManager); row.setLabelText(rank.getLabelText()); countManager.setRow(rank, row); nodes.add(row); });
これで、FXMLを小さくして柔軟な変更が可能なアプリケーションを作ることができます。
ポイント
部品ごとにFXMLを小分けにするときは、だいたい次のようにやっています。
- コントローラークラスは
root
要素のtype
属性を継承したクラスにする - コンストラクターの中でFXMLのURLを元に
FXMLLoader
のインスタンスを作る FXMLLoader
のインスタンスに自分がルートであり、コントローラーであると認識させる
メリット
FXMLを小分けにするメリットは
- 柔軟な変更が可能になる
- いらなくなったら捨てれば良い
といったところです。
デメリット
当然デメリットも有ります。
- 必要なオブジェクトの依存関係の把握が結構面倒臭い
必要なオブジェクトの云々についてはDIライブラリー(Guiceなど)を使えば解決できると思うので、僕としてはそれほどデメリットに感じていません。
以上、あまりガチな内容ではありませんでしたが、JavaFXアプリケーションを作るときにFXMLを小さくする方法について書いてみました。
あと、この方法ググった時に日本語情報なかったけど、検索キーワード悪かったのかな…(´・ω・`)