Scene Builder 小ネタ 3つ

このEntryはJavaFX Advent Calendarの11日目です。

昨日は @Yucchi_jp さんの HitInfoを少しだけ… でした。

明日は @boochnich さんです。

すいません、小ネタです。

Scene Builder をビルドする

みなさん、Scene Builder使ってますか? 便利ですよね。

でも、Oracleバイナリパッケージを配布しなくなってしまったのが... めんどくさいんですかねぇ。まぁ、Gluonがバイナリパッケージを配布してくれているからいいのですが。

とはいえ、せっかくのOSSなのですから、ビルドしてみたいと思いませんか。

JavaFX、というかOpenJFXをビルドするにはいろいろと準備が必要なのでめんどうなんですけど、Scene Builderだけであれば簡単です。

さっそくやってみましょう。

なお、今回はJava SE 8用のScene Builderをビルドします。

まずは、OpenJFXをクローンします。残念ながら、Scene Builderだけをクローンすることはできないのです。ちなみに、OpenJDKが使っているのはMercurialです。

hg clone http://hg.openjdk.java.net/openjfx/8u-dev/rt

クローンが完了しましたか。そうしたら、rt/apps/scenebuilderを見てみましょう。SceneBuilderAppディレクトリがScene BUilder本体、SceneBuilderKitディレクトリがIDEに組み込むなどの用途で使用するライブラリになってます。

OpenJFXのビルドにはgradleを使うのですが... なんとSceneBuilderAppのディレクトリを見てみるとnbprojectというディレクトリがあります。

これが何を意味するかというと、NetBeans用のプロジェクトになっているということです。

さっそくNetBeansで読み込んでみましょう。

SceneBuilderApp単独ではビルドできないので、SceneBuilderKitも一緒に読み込みます。

後は、SceneBuilderAppプロジェクトでF6すれば、ビルドして実行します。

JavaFXのアプリケーションは実行するとJARファイルも作ってくれます。distディレクトリにSceneBuilderApp.jarファイルができているはずです。後は-jarオプションで実行すればOK。

java -jar SceneBuilderApp.jar

とはいえ、dist/libにあるSceneBuilderKit.jarも一緒に使ってます。なので、他の場所にコピーする時はSceneBuilderKit.jarも忘れずに。

ルートコンテナを変更する

たとえば、NetBeans で FXML を生成すると、勝手に AnchorPane をルートコンテナとする FXML を生成しますよね。「空のFXML」を生成するといっても、ルートコンテナだけは勝手に設定してしまいます。

でも、AnchorPane を使いたいわけじゃないんだよ、他のコンテナを使いたいんだよ、ということも多いのでは。

そんな時、どうするかというと、IDEテキストエディタで FXML を編集すればいいのですが... そんな時でも、Scene Builder を使いたいわけです。

Scene Builder には Wrap in という機能があります。これを使えば、ルートコンテナを変更することもできるのです。

Wrap in は任意のノードをコンテナで包み込む機能です。

たとえば、AnchorPane に Button が貼られているとします。この Button を、AnchorPane の直下ではなく、間にコンテナ、たとえば FlowPane を挟みたいような場合に使用します。

これをやるには、Wrap in したいノードを選択します。選択するのは Hierarchy の処でも、中央のエディタ部分でもどちらでも OK。

そして、右クリックでポップアップメニューを表示させ、[Wrap in] を選択します。すると、下の図のようにコンテナの一覧がサブメニューに表示されるので、使用したいコンテナを選択します。

ノードを直接右クリックしなくても、メニューバーの [Arrange] - [Wrap in] でもおなじことができます。

Wrap in すると、ノードとコンテナの間に、指定したコンテナが挟まります。

この機能、どんなノードに対してもできるので、コンテナに対してもできます。つまり、ルートコンテナでも OK ということです。

ルートコンテナに対して Wrap in をしても、ちゃんと名前空間の定義やコントローラクラスへのリンクが保ったままにしてくれます。

ただし、スタイルシートは引き継がないので、注意が必要です。

たとえば、次のような FXML があったとしましょう。

<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" 
            xmlns:fx="http://javafx.com/fxml/1" fx:controller="ViewController">
    <stylesheets>
        <URL value="@view.css"/>
    </stylesheets>
</AnchorPane>

これを BorderPane で Wrap in すると、次のようになります。

<BorderPane xmlns="http://javafx.com/javafx/8.0.60" 
            xmlns:fx="http://javafx.com/fxml/1" 
            fx:controller="ViewController">
   <center>
      <AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0">
          <stylesheets>
              <URL value="@view.css" />
          </stylesheets>
      </AnchorPane>
   </center>
</BorderPane>

CSS の view.css がルートコンテナに引き継がれていません。また、サイズは設定されていないのは、しかたないですね。

コントローラクラスの雛形を作る

「コントローラクラスの雛形を作るのにいい方法ないですか」と FB で質問されたのですが、意外にみなさん Scene Builder の機能を知らないのですね ^ ^;;

コントローラクラスの雛形を作るのは簡単。

メニューバーの [View] - [Show Sample Controller Skelton] を選択すれば OK です。

たとえば、某連載用に作った次の 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>

この FXML は fx:id で結びつけられた要素が 2 つ (keyField, valueLabel)、イベント処理が 1 つ (searchメソッド) あります。

では、[View] - {Show Sample Controller Skelton] を選択してみると...

というダイアログが表示されます。ファイルには落とせないので、左下の[コピー]を選択すれば、クリップボードに保存してくれるので、後はペーストするだけ。

でも、このスケルトン、間違っているんですよね ^ ^;;

何が違うかというと、ActionEvent の import 文が抜けてます。まぁ、import 文は IDE で補完してくれるから、問題ないといえば、ないのですが。

ちなみに、右下の [Full] を選択すると、コードが次のようになります。

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

public class DictionaryController {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private TextField keyField;

    @FXML
    private Label valueLabel;

    @FXML
    void search(ActionEvent event) {

    }

    @FXML
    void initialize() {
        assert keyField != null : "fx:id=\"keyField\" was not injected: check your FXML file 'dictionaryView.fxml'.";
        assert valueLabel != null : "fx:id=\"valueLabel\" was not injected: check your FXML file 'dictionaryView.fxml'.";

    }
}

これだと、ちゃんと ActionEvent の import 文も含まれています。

ちなみに、initializeメソッドが Initializable インタフェースの initialize メソッドではないところに注意が必要ですね。

ところで、このダイアログ、違和感ないですか?

今の Scene Builder は L8N されていないのですが、なぜかこの [コピー] ボタンだけ日本語になっているのです。なんでなんでしょうね?