最終更新日:180112 原本2016-12-22 

JavaFXのGUI構築ツール、Scene BuilderでFXML編集

 JavaFXでのGUIの構造とイベント駆動について、前回まで3回にわたって解説してきました。FXMLとコントローラクラスを使用することで、構造をFXML、イベント処理をコントローラクラスを分離して記述できます。

 しかし、FXMLをエディタで記述することはなかなか大変です。そこで、今回はJavaFXのGUI構築ツールであるScene Builderについて紹介していきます。

Scene Builder

 Scene BuilderはOpenJDKのOpenJFXプロジェクトで開発されているGUI構築ツールです。ただし、OpenJFXからソースをダウンロードすることはできますが、バイナリーのパッケージは配布していません。その代わり、GluonがScene Builderのバイナリーパッケージを配布しています。本記事でもGluonが配布しているパッケージを使用します。

 Scene BuilderはGluonのScene Builderのページからダウンロードします。Windows/OS X/Linuxのパッケージが配布されているので、お使いのOSに応じたパッケージをダウンロードしてください。

 本記事ではWindows (x64)を使用します。本記事の執筆時点でのバージョンは8.2.0で、Windows (x64)用のパッケージはSceneBuilder-8.2.0-x64.exeとなります。パッケージをダウンロードしたら、実行してScene Builderをインストールしてください。

 Scene BuilderはFXMLをグラフィカルに編集するためのツールです。このため、コントローラクラスなどJavaのコードは編集できません。Scene Builderは単独で使用せず、ほとんどはIDEと組み合わせて使います。

 本記事ではNetBeans IDE 8.2をScene Builderと組み合わせて使用します。NetBeansからScene Builderを使用するための設定は、特にありません。Scene Builderをインストールすれば、すぐに使えます。

JavaFXアプリケーションの作成

 では、NetBeansとScene Builderで、JavaFXのアプリケーションを作成してみましょう。作成するのは、前回サンプルとして使用したキーを入力すると それに対応した値をラベルに出力するアプリケションです。

 NetBeansでJavaFXアプリケーションを作成するには、まずプロジェクトを作成します。メニューバーの「ファイル」→「新規プロジェクト」を選択するか、プロジェクトペインで右クリックし、ポップアップメニューの「新規プロジェクト」を選択します。

 すると、「新規プロジェクト」ダイアログが表示されます。JavaFXアプリケーションを作成するには、「カテゴリ」のJavaFXから「JavaFXアプリケーション」もしくは「JavaFX FXMLアプリケーション」を選択します(図1)。

図1●新規プロジェクトダイアログ
[画像のクリックで拡大表示]

 ここでは「JavaFX FXMLアプリケーション」を使用してプロジェクトを作成してみます。なお、「JavaFXアプリケーション」で作成したプロジェクトでも、FXMLの使用は可能です。

 次にプロジェクトの名前などを指定します(図2)。ここではプロジェクト名を「dictionary」としました。また、FXML名は前回作成したように「dictionaryView」としています。ここでは拡張子のfxmlは省略します。最後のアプリケーションクラス名も前回通り、「DicationaryApp」としました。

図2●プロジェクトの名前と場所
[画像のクリックで拡大表示]

 これで、プロジェクトが作成できました。プロジェクトを作成すると、アプリケーションクラスとFXML以外にコントローラクラスが自動的に作成されます(図3)。コントローラクラスはFXML名にControllerを付加した名前となります。

図3●dictionaryプロジェクト

 自動生成されたDictionaryAppクラスをリスト1に示しました(コメントは省略しました)。

リスト1●DictionaryAppクラス

public class DictionaryApp extends Application {
    
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("dictionaryView.fxml"));
        
        Scene scene = new Scene(root);
        
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

 前回作成したDictionaryAppクラスとの違いは、ルートコンテナの型がVBoxクラスではなく、その継承元であるParentクラスになっていることです。これはこのままでも動作するので、そのままにしておきましょう。気になる方は、具象クラスであるVBoxクラスに変更してみてください。

 また、ステージのタイトルが設定されていないので、それだけは設定することにしましょう。

FXMLの編集

 では、FXMLの編集に移りましょう。NetBeansのプロジェクトペインでdictionaryView.fxmlをダブルクリックしてください。すると、Scene Builderが立ち上がります。初回起動時は図4に示したダイアログが表示されます。

図4●登録ダイアログ

 この登録は任意なので、登録したくない場合は「Cancel」ボタンをクリックしてください。Scene Builderは図5に示したように、4つのペインに分割されています。

図5●Scene Builder
[画像のクリックで拡大表示]

 左上のペインには描画要素の一覧があります。ここから任意の要素を選択して、編集ペインもしくはシーングラフペインにドラッグ・アンド・ドロップできます。

 シーングラフペインは作成しているGUIのツリー構造を表示します。シーングラフを構成している要素を選択すれば、編集ペインでも選択状態になります。

 中央の編集ペインでは、グラフィカルにGUIを編集できます。右側のプロパティペインは選択した描画要素のプロパティの一覧が表示されます。もちろん、プロパティを編集することも可能です。図5でプロパティペインに何も表示されていないのは、要素を選択していないためです。

ルートコンテナの変更

 自動作成されたdictionaryView.fxmlはルートコンテナがAnchorPaneクラスで、そこにボタンとラベルが貼ってあります。しかし、前回作成したdictionaryView.fxmlはルートコンテナがVBoxクラスでした。そこで、まずAnchorPaneクラスからVBoxクラスにルートコンテナを変更してみましょう。

 それにはAnchorPaneを選択します。選択は編集ペインでButton以外の白色の部分をクリックするか、シーングラフペインのAnchorPaneをクリックします。次に、メニューバーの「Arrange」→「Wrap in」を選択します。すると、サブメニューが開き、コンテナクラスの一覧が表示されます(図6)。今回は、この中からVBoxを選択します。

 「Wrap in」は選択している要素をラップするコンテナを設定するということです。ルートコンテナを選択して「Wrap in」すれば、新たなルートコンテナを設定できます。

図6●Wrap in

 さて、VBoxで「Wrap in」した後のシーングラフは図7のようになりました。

図7●Wrap in後のシーングラフ

 VBoxがルートコンテナになっていることが分かります。ルートコンテナが設定できれば、AnchorPane以下は使用しないので、削除しましょう。削除は描画要素を選択して、右クリックし、ポップアップメニューの「Delete」を選択します。選択状態で、Deleteキーでも削除できます。

 AnchorPaneを削除してしまうと、編集ペインには何も表示されなくなってしまいました。これは、VBoxのサイズを指定していないためです。サイズはプロパティペインから設定できます。シーングラフペインでVBoxを選択し、プロパティペインの「Layout」カテゴリーを選択します(図8)。

図8●VBoxのLayoutカテゴリー

 前回、FXMLでprefWidthプロパティとprefHeightプロパティを設定したことを思い出してください。ここでも、赤四角で囲ったPref WidthプロパティとPref Heightプロパティを設定します。前回と同様に、Pref Widthを400、Pref Heightを100に設定します。設定すると、編集ペインに表示がされるはずです。

 この時点でdictionaryView.fxmlはどのようになっているでしょう。Scene BuilderでFXMLを保存してから、NetBeansでFXMLを見てみましょう。

 NetBeansのプロジェクトペインでdictionaryView.fxmlを右クリックし、「編集」を選択します。すると、エディターにdictionaryView.fxmlが表示されます。リスト2にこの時点でのdictionaryView.fxmlを示しました。なお、リスト2は読みやすさのために適宜改行し、XML宣言などは省略してあります。

リスト2●dictionaryView.fxml

<VBox prefHeight="100.0" prefWidth="400.0" 
      xmlns="http://javafx.com/javafx/8.0.60" 
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="dictionaryViewController" />

 リスト2には前回指定しなかったデフォルトでのスキーマが指定してあります。前回は指定しなかったように、この部分は必須ではないのですが、お約束としてそのままにしておきましょう。

描画要素の配置

 ではVBoxに要素を貼っていきましょう。まずは要素ペインの「Containers」カテゴリーからHBoxを、VBoxにドラッグ・アンド・ドロップします。編集ペインではドラッグできる部分はオレンジで表示されるので、すぐに分かるはずです。シーングラフペインのVBoxにドラッグ・アンド・ドロップすることも可能です。

 次にHBoxの下に表示させるLabelです。Labelは要素ペインの「Controls」カテゴリーにあるので、これもドラッグ・アンド・ドロップします。編集ペインにドラッグ・アンド・ドロップすると、場所によってはHBoxに貼ってしまうこともあるかもしれません。そういう場合は、シーングラフペインにドラッグ・アンド・ドロップすると、やりやすいはずです。

 Labelを貼ってみると、その配置場所が左側に偏るはずです(図9)。

図9●VBoxのalignmentプロパティ
[画像のクリックで拡大表示]

 これはVBoxのデフォルトのAlignmentプロパティがTOP_LEFTになっているためです(図9の赤四角部分)。Alignmentプロパティは前回も紹介しましたが、コンテナにおける子要素の配置を決めるためのプロパティです。

 Alignmentプロパティはプロパティペインの「Properties」カテゴリーにあるので、これを前回同様CENTERに変更します。こうすれば、中央に表示されるはずです。同様にHBoxもAlignment属性がTOP_LEFTになっているので、こちらもCENTERに変更しましょう。

 では、HBoxにコントロールを貼っていきましょう。まずはTextFieldをHBoxにドラッグ・アンド・ドロップし、同様にButtonもドラッグ・アンド・ドロップします。いずれも要素ペインの「Controls」カテゴリーに配置されています。Buttonまで貼った後の編集ペインを図10に示しました。

図10●描画要素

プロパティの設定
 全ての部品を配置できたので、後は個々の要素のプロパティを設定していきます。ButtonやLabelに表示する文字列は、編集ペインでダブルクリックをすると編集できます。また、プロパティペインのTextプロパティでも編集できます。

 ButtonのTextプロパティは「Search」、LabelのTextプロパティは空にします。TextFieldはPrompt Textプロパティを「Key」にします。Prompt Textプロパティは前回紹介したように、何も入力していない時に表示する文字列です。

 さらにPref Column Countプロパティも設定します。Pref Column Countプロパティは「Lyaout」カテゴリーにあります。前回同様、この値は20としましょう。最後にHBoxとLabelの間のスペースと、TextFieldとButtonの間のスペースを調整します。

 いずれもVBoxとHBoxのSpacingプロパティで設定します。Spacingプロパティは「Layout」カテゴリーにあります。VBoxのSpacingプロパティは12、HBoxのそれは20とします。

 これでGUIの構成とプロパティの設定はできました。リスト3にdictionaryView.fxmlを示しました。

リスト3●dictionaryView.fxml

<VBox alignment="CENTER" prefHeight="100.0" prefWidth="400.0" spacing="12.0" 
      xmlns="http://javafx.com/javafx/8.0.60" 
      xmlns:fx="http://javafx.com/fxml/1" 
      fx:controller="dictionaryViewController">
   <children>
      <HBox alignment="CENTER" spacing="20.0">
         <children>
            <TextField prefColumnCount="20" promptText="Key" />
            <Button mnemonicParsing="false" text="Search" />
         </children>
      </HBox>
      <Label />
   </children>
</VBox>

 前回示したditionaryView.fxmlとほぼ同一のはずです。

コントローラクラスの作成
 ここまでFXMLができたら、今度はコントローラクラスに移りましょう。自動生成されたクラスは、FXMLのファイル名を小文字で始めてしまったため、クラス名が小文字で始まってしまっています。これが気になるので、まず名前を前回同様DictionaryControllerクラスに変更します。

 クラス名の変更にはリファクタリングで行うか、エディタでクラス名にカーソルがある時に[Ctrl]+[R]キーを押下します。NetBeansはJavaのコードでこのクラスを使用している部分は更新するのですが、FXMLは更新しません。これは後ほどScene Builderで変更しましょう。

 さて、DictionaryControllerクラスは前回提示したものと同じとします。前回も提示しましたが、リスト4にdictionaryViewControllerクラスを示します。もちろん、モデルとなるDictonaryクラスも作成します。

リスト4●DictionaryControllerクラス

public class DictionaryController implements Initializable {
    // モデル
    private Dictionary dictionary;
    @FXML
    private TextField keyField;
    @FXML
    private Label valueLabel;
    
    // イベントハンドラメソッド
    @FXML
    private void search(ActionEvent event) {
        String value = dictionary.get(keyField.getText());
        valueLabel.setText(value);
    }
    
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // モデルの初期化
        dictionary = new Dictionary();
    }
}

 また、Dictionaryクラスもリスト5に再掲します。

リスト5●Dictionaryクラス

public class Dictionary {
    private final static String NO_KEY_VALUYE = "No Registered Value";
    private Map<String, String> dictionary;
    
    public Dictionary() {
        dictionary = new HashMap<>();
        dictionary.put("Bob Dylan", "Blowin' in the Wind");
        dictionary.put("Pete Seeger", "We shall Overcome");
        dictionary.put("Woody Guthrie", "This Land is Your Land");
        dictionary.put("Peter, Paul & Mary", "500 miles");
        dictionary.put("Kingstone Trio", "Where Have All the Flowres Gone?");
        dictionary.put("Everly Brothers", "Bye Bye Love");
    }
    
    public String get(String key) {
        return dictionary.getOrDefault(key, NO_KEY_VALUYE);
    }
}

FXMLとコントローラクラスの対応づけ

 コントローラクラスができたら、再びScene Builderに戻りましょう。Scene Builderでは前回紹介したコントローラクラスとの対応付けの編集もできます。まずはコントローラクラスを指定しましょう。

 それには左下の「Controller」カテゴリーから行います。「Controller」カテゴリーはシーングラフを表示する「Hierachy」カテゴリーの下にあります(図11)。

図11●Controllerカテゴリー

 Controller classには変更前のクラス名であるdictionaryViewControllerが表示されているはずです。これを編集してDictionaryControllerとしましょう。

 次にIDの割り付けです。描画要素にIDを振るには、プロパティペインの「Code」カテゴリーのfx:idプロパティで行います。コントローラクラスが正しく設定してあれば、fx:idプロパティにプルダウンボタンが表示されるはずです。

 このボタンをクリックすると、コントローラクラスにおける結び付けることが可能なフィールドの一覧が表示されます(図12)。

図12●コントローラクラスの指定

 TextFieldにはkeyField、LabelにはvalueLabelを選択します。

 最後にButtonがクリックされた時のイベントハンドラの指定です。図12にOn Actionプロパティが表示されていますが、ここで指定を行います。Buttonを選択して、On Actionプロパティのプルダウンボタンをクリックすると、searchメソッドが表示されます。これを選択すれば、イベントハンドラを指定できます。

 dictionaryView.fxmlの最終形をリスト6に示しました。

リスト6●dictionaryView.fxml

<VBox alignment="CENTER" prefHeight="100.0" prefWidth="400.0" spacing="12.0" 
      xmlns="http://javafx.com/javafx/8.0.60"
      xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="DictionaryController">
   <children>
      <HBox alignment="CENTER" spacing="20.0">
         <children>
            <TextField fx:id="keyField" prefColumnCount="20" promptText="Key" />
            <Button mnemonicParsing="false" onAction="#search" text="Search" />
         </children>
         </HBox>
      <Label fx:id="valueLabel" />
   </children>
</VBox>

 では、NetBeansでプロジェクトを実行してみましょう。起動時の表示を図13、テキストフィールドに「Pete Seeger」を入力して、Searchボタンをクリックした後を図14に示しました。

図13●DictionaryAppの起動画面
図14●値の表示

終わりに

 今回は、FXMLをグラフィカルに編集することのできるScene Builderを紹介しました。

 FXMLを手書きでは編集することは大変ですし、スペルミスなどの間違いも発生しやすくなります。これに対し、Scene Builderを使うことで、簡単に編集ができます。また、編集した結果も、その場で反映されるので見た目がどのようになるのかが把握しやすくなります。スペルミスなどは発生しにくくなるので、デバッグの時間も短くなるはずです。

 とはいうものの、Scene BuilderだけではFXMLしか編集できません。Javaの部分はIDEに任せる必要があります。本記事ではNetBeansを使いましたが、IntelliJ IDEAなどでもScene Builderを使えます。

 Scene Builderには、今回紹介しきれなかった機能もいろいろあります。ぜひ、一度試しに使ってみてください。

前回はシュトーレンについて書いたので、今回はイギリスのクリスマスプディングです。プディングといっても、カスタードのプリンとは全然違います。ドライフルーツがぎっしりと入った蒸しケーキです。

写真のように見た目はあまりよくないのですが、これがおいしいのです。食べるときに再度蒸して、温めて食べます。蒸しているときにドライフルーツの香りが漂ってきて、それだけで食欲をそそります。

櫻庭は店でカットしたクリスマスプディングを食べたことはあったのですが、自宅で食べたのは初めて。来年もリピート確実です。

[画像のクリックで拡大表示]