JavaFX2.2でダイアログを作る方法

はじめに

TwitterJavaFX関連の話になったときにしばしば見かけるのが、「ダイアログ表示できないの?」というコメントです。
JavaFXにはOSレベルでのウィンドウを作成するための Stage クラスがあり (Swingの JFrame 相当のクラスです) 、これを使えばダイアログの作成は可能です。
でも、Swingの JOptionPane に相当するユーティリティクラスは残念ながらありません。
次のバージョンに当たるJavaFX8ではSandboxプロジェクトで開発されていたのですが、いつの間にやら ControlsFX というオープンソースプロジェクトに独立していました。これもJavaFX8にならないと使えません。

と言うわけでJavaFX2ではダイアログを表示するためには自分で実装する必要があるのですが、一度やり方を覚えてしまえばまあそんなに面倒でもないです。
また、現在ベータ版の Scene Builder1.1 ではダイアログを作成するためのひな形が用意されていたりします。
ここではScene Builderのひな形を使ってダイアログを作成する方法についてまとめてみます。

今回作ってみるダイアログ

今回は懐かしのSwingSet2にあったダイアログを作ってみましょう。以下に示すものです。

Yesと答えると「コンピュータの前にいないで外へ出ろ」と説教されるやつですw
YesとNoについてはそれぞれ新たなメッセージダイアログが次に開き、Cancelが選ばれたら何も表示せずにそのまま閉じます。
Yesがデフォルトボタンに設定されていて、キーボードでEnterを押すとYesが選択されます。また、Escを押すとキャンセル扱いになり、そのまま閉じます。

Scene Builderでダイアログのデザインを作る

ではJavaFXでこれを作ってみましょう。
先にも述べたように、Scene Builder1.1 にはダイアログのひな形が用意されています*1。メニューから [ファイル] - [テンプレートから新規作成] - [アラートダイアログ] を選択します。

すると次のようにダイアログのひな形になるFXMLが生成されます。

ちなみにメニューのスクリーンショットを見てお気づきになったかと思いますが、CSSやリソースバンドルを一緒に生成してくれるメニューもあります。
これをベースに自分の好みに合わせてちょいちょいといじっていけばいいわけです。今回は次のように仕上げました。

具体的に行った変更点は次の通りです。

  • ImageView部分に60×60の画像を設定。
    • 今回はCacooを使って自作しました。
  • 文章だけを表示したいので、太字のタイトルラベルを削除。
  • Yesボタンをデフォルトボタンに設定。
    • プロパティの [Default Button] にチェックを入れます。こうすると、Enterキーを押されたときに選択されるボタンに設定されます。
  • Cancelボタンをキャンセルボタンに設定。
    • プロパティの [Cancel Button] にチェックを入れます。こうすると、Escキーを押されたときに選択されるボタンに設定されます。

FXMLは次のようになりました。

<GridPane hgap="14.0" maxHeight="+Infinity" maxWidth="+Infinity" minHeight="-Infinity" minWidth="-Infinity" vgap="20.0" xmlns:fx="http://javafx.com/fxml" fx:controller="aoetk.dialogsample.ConfirmDialogController">
  <children>
    <ImageView fitHeight="60.0" fitWidth="60.0" pickOnBounds="true" preserveRatio="true" GridPane.columnIndex="0" GridPane.halignment="CENTER" GridPane.rowIndex="0" GridPane.valignment="TOP">
      <image>
        <Image url="@confirm.png" />
        <!-- place holder -->
      </image>
    </ImageView>
    <VBox alignment="CENTER_LEFT" maxHeight="+Infinity" maxWidth="+Infinity" minHeight="-Infinity" prefWidth="400.0" spacing="7.0" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.valignment="CENTER">
      <children>
        <Label fx:id="detailsLabel" text="message" textAlignment="LEFT" wrapText="true">
          <font>
            <Font name="HGPGothicE" size="13.0" />
          </font>
        </Label>
      </children>
    </VBox>
    <HBox maxHeight="-Infinity" maxWidth="+Infinity" minHeight="-Infinity" minWidth="-Infinity" prefWidth="300.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
      <children>
        <HBox id="HBox" alignment="CENTER">
          <children>
            <Button id="btnCancel" cancelButton="true" mnemonicParsing="false" onAction="#handleBtnCancelAction" text="Cancel" HBox.hgrow="NEVER">
              <HBox.margin>
                <Insets right="14.0" />
              </HBox.margin>
            </Button>
          </children>
          <HBox.margin>
            <Insets />
          </HBox.margin>
        </HBox>
        <Pane maxWidth="+Infinity" HBox.hgrow="ALWAYS" />
        <Button id="btnNo" cancelButton="false" minWidth="80.0" mnemonicParsing="false" onAction="#handleBtnNoAction" text="No" HBox.hgrow="NEVER">
          <HBox.margin>
            <Insets />
          </HBox.margin>
        </Button>
        <HBox id="HBox" alignment="CENTER">
          <children>
            <Button id="btnYes" defaultButton="true" minWidth="80.0" mnemonicParsing="false" onAction="#handleBtnYesAction" text="Yes" HBox.hgrow="NEVER">
              <HBox.margin>
                <Insets left="14.0" />
              </HBox.margin>
            </Button>
          </children>
        </HBox>
      </children>
    </HBox>
  </children>
  <columnConstraints>
    <ColumnConstraints hgrow="NEVER" maxWidth="-Infinity" minWidth="-Infinity" />
    <ColumnConstraints halignment="CENTER" hgrow="ALWAYS" maxWidth="+Infinity" minWidth="-Infinity" />
  </columnConstraints>
  <padding>
    <Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
  </padding>
  <rowConstraints>
    <RowConstraints maxHeight="+Infinity" minHeight="-Infinity" valignment="CENTER" vgrow="ALWAYS" />
    <RowConstraints maxHeight="-Infinity" minHeight="-Infinity" vgrow="NEVER" />
  </rowConstraints>
</GridPane>

同じようにして、メッセージダイアログのデザインも作成します。

Javaコード側の実装

さて、お次はJavaコード側の実装です。まずは先ほど作ったダイアログのFXMLに対してコントローラーを作ります。

public class ConfirmDialogController implements Initializable {
    @FXML
    private Label detailsLabel;
    private DialogOption selectedOption = DialogOption.CANCEL;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }

    public DialogOption getSelectedOption() {
        return selectedOption;
    }

    public void setMessage(String msg) {
        detailsLabel.setText(msg);
    }

    @FXML
    void handleBtnYesAction(ActionEvent event) {
        handleCloseAction(DialogOption.YES);
    }

    @FXML
    void handleBtnNoAction(ActionEvent event) {
        handleCloseAction(DialogOption.NO);
    }

    @FXML
    void handleBtnCancelAction(ActionEvent event) {
        handleCloseAction(DialogOption.CANCEL);
    }

    private void handleCloseAction(DialogOption selectedOption) {
        this.selectedOption = selectedOption;
        getWindow().hide();
    }

    private Window getWindow() {
        return detailsLabel.getScene().getWindow();
    }
}

注目するのは各ボタンに割り当てられたハンドラメソッド (handleBtnYesAction、handleBtnNoAction、handleCancelYesAction) です。
選択結果を自身のプロパティに保存した後に、ダイアログ上のコンポーネントから Scene 、Window をたどって、自身のウィンドウを隠しています (handleCloseAction メソッド) 。
また、ダイアログに表示するメッセージはコントローラーが受け取るようにしています。
メッセージダイアログのコントローラーについても大体これと同じような実装になっています。

続いてダイアログを開く側のコードです。

    @FXML
    private void handleButtonAction(ActionEvent event) {
        try {
            // 確認ダイアログの表示
            FXMLLoader loader = new FXMLLoader(getClass().getResource("ConfirmDialog.fxml"));
            loader.load();
            Parent root = loader.getRoot();
            ConfirmDialogController controller = loader.getController();
            controller.setMessage("今日の外の天気は晴れですか?");
            Scene scene = new Scene(root);
            Stage confirmDialog = new Stage(StageStyle.UTILITY);
            confirmDialog.setScene(scene);
            confirmDialog.initOwner(button.getScene().getWindow());
            confirmDialog.initModality(Modality.WINDOW_MODAL);
            confirmDialog.setResizable(false);
            confirmDialog.setTitle("Select an Option");
            confirmDialog.showAndWait(); // ダイアログが閉じるまでブロックされる

            // 確認ダイアログの選択結果に応じたメッセージダイアログの表示
            switch (controller.getSelectedOption()) {
            case YES:
                showMessageDialog("コンピュータで遊んでないで外に出よう。\nビーチに行って太陽の日を浴びたらどうでしょう。");
                break;
            case NO:
                showMessageDialog("屋内にいて様々なものから保護されているのはいいことです。");
                break;
            }

        } catch (IOException ex) {
            Logger.getLogger(DialogSampleViewController.class.getName()).
                    log(Level.SEVERE, "読み込み失敗", ex);
        }
    }

FXMLLoader を使って、確認ダイアログのFXMLを読み込んでいます。
このコード例で示しているように、FXMLLoader からコントローラーのインスタンスを取得することが可能で、表示したいメッセージを渡していますね。
注目してもらいたいのは Stage のインスタンスを生成している部分です。

            Stage confirmDialog = new Stage(StageStyle.UTILITY);
            confirmDialog.setScene(scene);
            confirmDialog.initOwner(button.getScene().getWindow());
            confirmDialog.initModality(Modality.WINDOW_MODAL);
            confirmDialog.setResizable(false);
            confirmDialog.setTitle("Select an Option");
            confirmDialog.showAndWait(); // ダイアログが閉じるまでブロックされる

StageStyle.UTILITY を渡してインスタンスを生成していますが、これはウィンドウの装飾を最小限にするというオプションです。つまり、ウィンドウを閉じるボタンしか表示されません。*2
また、initOwner メソッドを使って、親ウィンドウのインスタンスを渡しておき、さらに initModality メソッドModality.WINDOW_MODAL を渡す事で、親ウィンドウに対してモーダルになるように設定しています。
そして、Stage#showAndWait メソッドを使ってダイアログウィンドウを表示しますが、このメソッドはウィンドウが閉じられるまでスレッドをブロックします。

従って、showAndWait 呼び出しの後はダイアログが閉じられた状態になっているので、次のように確認ダイアログコントローラーが保持している選択肢を取得して、次のメッセージダイアログに表示するメッセージを決めて、ダイアログを表示しています。

            switch (controller.getSelectedOption()) {
            case YES:
                showMessageDialog("コンピュータで遊んでないで外に出よう。\nビーチに行って太陽の日を浴びたらどうでしょう。");
                break;
            case NO:
                showMessageDialog("屋内にいて様々なものから保護されているのはいいことです。");
                break;
            }

showMessageDialog メソッドについてはもう説明するまでもないでしょう。(最後に全コードをアップしたgistのURLを示します)

このコードを実行すると次のように動きます。

とまあこんなところです。一度やり方を覚えてしまえばまあそんなに手間でもないでしょう。たぶん...きっと...。
JavaFX8、Java8に先駆けて先行リリースされないかなあ。

全コードはgistにアップしています。ご参照ください。(画像は自分で用意してくださいね)
https://gist.github.com/aoetk/5652577

*1:Scene Builderで表示されるダイアログもこのひな形を使って作っているようです。

*2:ちなみに StageStyle.UNDECORATED にすると、ウィンドウのボタンが一切表示されなくなります。ダイアログにはこっちの方がいいかも知れません。(Macのダイアログはそのようになっていますね)

FX GlassFish Monitorの解説 (見映え編)

Java The Nightのデモでお見せしたFX GlassFish Monitorの作りについての解説です。まずは反響が大きかった見映えのところから説明したいと思います。

一番目に付いたのはウィンドウ枠だと思います。OSのウィンドウ枠は全く見えず、周囲が何か光っていますね。
JavaFXではコンポーネントにドロップシャドウエフェクトを追加することができ、今回もそれを利用しているのですが、OSのウィンドウ枠に相当する Stage クラスには適用することはできません。

そこで次のような方法で実現しました。

  • Stage 及び Scene は透明にする。
  • その上に、ドロップシャドウエフェクトを効かせた Rectangle を貼り付ける。
    • この Rectangle の大きさは Scene よりシャドウの幅の分だけ小さくする。
  • その上にレイアウトコンテナ (BorderPaneAnchorPane) を貼り付ける。

図示するとこんな感じです。

メインウィンドウ (監視項目のツリーが並ぶウィンドウ) について、Stage の上に Scene を組み立てている部分のJavaコードと、FXML のコードを示しておきます。

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("view/MainView.fxml"));
        loader.load();
        Parent root = loader.getRoot();
        MainViewController controller = loader.getController();
        controller.setParentStage(stage);
        Scene scene = new Scene(root, 924, 700, Color.TRANSPARENT);
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setScene(scene);
        stage.show();
    }
<StackPane id="StackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="924.0" xmlns:fx="http://javafx.com/fxml" fx:controller="aoetk.fxglassfishmonitor.view.MainViewController">
  <children>
    <Rectangle arcHeight="5.0" arcWidth="5.0" fill="#1e90ff00" height="690.0" stroke="$x1" strokeType="INSIDE" width="914.0">
      <effect>
        <DropShadow blurType="GAUSSIAN" spread="0.7">
          <color>
            <Color blue="0.878" green="1.000" red="0.000" fx:id="x1" />
          </color>
        </DropShadow>
      </effect>
    </Rectangle>
    <BorderPane fx:id="containerPane" onMouseDragged="#handleMouseDragged" onMousePressed="#handleMousePressed" prefHeight="200.0" prefWidth="200.0" styleClass="container">
      <center>
        <ScrollPane prefHeight="200.0" prefWidth="200.0">
          <content>
            <Pane fx:id="drawRegion" prefHeight="200.0" prefWidth="200.0" />
          </content>
        </ScrollPane>
      </center>
      <top>
        <HBox fx:id="boxTitle" prefHeight="50.0" prefWidth="200.0">
          <children>
            <Text fill="$x1" fontSmoothingType="LCD" stroke="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="FX GlassFish Monitor" HBox.hgrow="NEVER">
              <font>
                <Font name="System Bold Italic" size="28.0" />
              </font>
            </Text>
            <Region prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS" />
            <Button fx:id="btnExit" mnemonicParsing="false" onAction="#handleBtnExitAction" styleClass="close-button" text="Exit" HBox.hgrow="NEVER" />
          </children>
          <padding>
            <Insets bottom="20.0" left="10.0" right="10.0" top="10.0" />
          </padding>
        </HBox>
      </top>
      <StackPane.margin>
        <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
      </StackPane.margin>
    </BorderPane>
  </children>
  <stylesheets>
    <URL value="@../asset/style.css" />
  </stylesheets>
</StackPane>

Rectangle とレイアウトコンテナ (BorderPane) の貼り合わせには StackPaneを使っています。
StackPane にマージンを指定していて、その上に置いたコンテンツがドロップシャドウの幅の分だけ隙間を空けるようにしています。

BorderPane の onMousePressed と onMouseDragged にイベントハンドラを設定しています。これは Stage を透明にしたために、そのままではウィンドウ枠が消えてウィンドウの移動ができなくなるため、自分でドラッグ移動のイベントを実装しているのです。

スタイルシートはこんな感じです。

.root {
    -fx-base: #212020;
}

.container {
    -fx-background-color: rgba(0, 0, 0, 0.7);
}

.close-button {
    -fx-border-color: white;
    -fx-border-width: 2px;
    -fx-border-radius: 5px;
    -fx-background-color: transparent;
    -fx-text-fill: white;
    -fx-font-size: 16px;
    -fx-cursor: hand;
}

container クラスがレイアウトコンテナに適用するもので、色を黒で透明度を 70% に設定しているだけです。
close-button クラスはExitボタンに適用したスタイルで、背景を透明にしてあのような見た目にしているのが分かりますね。

と言うわけで見映えについての解説でした。大して手数を掛けずに実現可能だと言うことが分かりますね。

コード全体についてはもう少し整理してからGithubのURLを公開する予定です。もうしばらくお待ちを。

おまけ

解説に使った図はScene Bulderを使って描きました。が、やっぱりお絵かきにはまだまだ機能不足ですね。Rectangle を平行四辺形に変形する部分は直接FXMLを手打ちしました...。

Java Day Tokyoに参加&発表してきました

5/14 (火) に開催されたJava Day Tokyoに参加し、さらにその中のセッションの1つである、Java The Nightに登壇しました。
まさかこんな大きなイベントで自分が発表する側に立つことになるとは思わず、とても緊張しましたが良い経験になりました。
このエントリではイベントの感想についてまとめたいと思います。

Java The Nightでの発表について

Java The Nightで自分は「監視ツールでみるJavaFXJava EEの魅力」と題して発表しました。セッション資料はSlideShareにアップしています。

Java Day Tokyo 2013 Java the Night 監視ツールでみるJavaFXとJava EEの魅力 from Takashi Aoe

登壇することになった経緯ですが、Oracle寺田さんからTwitterのダイレクトメッセージで突如お願いされましたw
寺田さんからの要望は、「デモの際におもしろ、おかしく、やっていただくことはできますでしょうか。」でした。
随分ハードル高いなあと思いつつも、折角の機会なので登壇させて頂くことになりました。

発表の内容としては、JavaFXで作ったGlassFishの監視アプリケーションのデモを通して、自分がJavaFXJava EEの魅力と思っている点について伝えるというものでした。
インパクト重視という要望だったので、JavaFXで作るアプリは派手めな、中二病っぽい感じの路線で行くことにしました。

JavaFXについてここで伝えたかったことは、プログラムを使って何かやりたいタスクがあったときに、JavaならばJavaFXが加わったことで、このように綺麗なGUIで成果をアウトプットできるようになるということです。
CUIもいいけど、GUIでやると楽しいですよ。
このアプリケーションのルックスについては予想以上に反響があったようですが、実はそんなに手数を掛けなくてもこのような見た目にすることが可能です。この点については後ほど別エントリで解説します。

Java EEについては、運用フェーズにおける監視という側面からの利点を伝えようとしました。Java EE APサーバーはいずれもサーバー上で稼働するサービス、アプリケーションに対して豊富な監視オプションを提供しています。
Webアプリケーションフレームワークはともすると開発の側面ばかりに目が行きがちですが、ソフトウェアのライフサイクル全体で考えると運用も重要です。その点も考慮して選択を考えてもらったらと思います。

各セッションについての感想

それでは自分が参加した各セッションについて軽く感想を書いていきます。

基調講演

基調講演ではJava SE、Java FX/Embedded、Java EE、コミュニティについて、それぞれのOracleのキーパーソンから現状と今後の展望について説明するというものでした。
特に自分の印象に残ったのが、OracleがM2Mを重視しているように見えたことです。今後は様々なデバイスがネットワークでつながるようになるため、確かに次にソフトウェア開発の分野でホットになるのはここかも知れません。そして恐らくこの分野でもライバルになるのはAndroidでしょうね...。

Raspberry Pi NightHacking

Pro JavaFX 2の著者の一人であるStephen Chinさんによる、Raspberry Pi上でのJavaFXアプリケーションの実行についてのセッションでした。
目の前でJavaFXアプリケーションを構築して、それをRaspberry Piに移植動かすところまでを見せてもらいました。
驚いたのは、本当にそのままのJavaFXアプリケーションが動いているということです。エフェクトとかも普通に動いていました。質問したところ、動かないのはWebViewとMediaViewだけとのことです。
Raspberry Pi向けのJava SEのプロファイルは結構小さいのですが、それでもJavaFXアプリケーションがここまで動くというのには色んな可能性を感じさせてもらいました。

Java IDE の最新トレンド

EclipseNetBeansIntelliJ IDEAというJava界三大IDEについて、Eclipse派代表として竹添さん、NetBeans派代表としてきしださん、IntelliJ派代表として今井さんが語り合うというとても楽しいセッションです。
モデレータの山本裕介さんがIntelliJユーザーと言うこともあり、ややIntelliJにバイアスが掛かっていたような気がしましたが。(^^;;
スライドが三者三様でこちらもそれぞれの個性が出ていて面白かったです。竹添さんはきっちりPowerPointで、今井さんはrstテキストで、そしてきしださんは安定の手書き資料w
なお、自分は基本的にNetBeans派ですが、IntelliJもちょこちょこ触りますし *1 、もちろん仕事の必要上Eclipseも結構触ります。
ディスカッションで自分の印象に残ったのは以下の点です。

  • Eclispeは確かにセッティングが面倒だけど、Eclipseが出たての頃はみんなその作業をとても楽しんでいた。
    • 確かに自分もEclipseが登場したての頃は、とてもわくわくしながら拡張を楽しんでいたのを覚えています。
  • InteillJはJava、Groovy、Scalaを1つのプロジェクトに混在させて開発できる。
  • NetBeansはJenkins連携ができる。ただしメニュー名は "Hudson" だけどw
  • (テキストエディタ派の人に向けて) IDEの利点は「IDEが良い書き方を教えてくれる」ところにありますよ。
Groovy, Clojure, Scala, VisageでのJavaFX活用

こちらもStephen Chinさんによるセッションです。Groovy、ClojureScalaVisage *2JRubyでのJavaFXのサポート状況について順に説明してもらいました。
ちなみにChinさんはScalaFXVisageのコミッタをされています。
JavaFXのサンプルアプリケーションとしてよく使われる、Vanishing Circleを例に、他の言語で実装した例を見せてもらいました。

特に自分がいいなと思ったのがGroovyFXです。@FXBindableアノテーションを付けることで、JavaFX形式のプロパティを生成してくれるのはとても嬉しい!
自分の以前のエントリでも解説しましたが、JavaFX形式のプロパティの記述はちょっと面倒なのです。
ScalaFXもとてもScalaらしいアプローチでDSLを設計していて、こちらも好感が持てました。
VisageJavaFX Script時代から言語仕様が強化されているのですね。

なお、Chinさんは日本の漫画やアニメが相当好きなようで、各言語についてアニメに例えてらっしゃいました。以下の通りですが自分のよく知らないモノもあるw

タブレット用の JavaFX アプリケーション開発

JavaFXエヴァンジェリストであり、Pro JavaFX2の著者の一人である、Jim Weaverさん *3 による、JavaFXのマルチタッチ関連APIの解説と、タブレット向けアプリケーションを作る上での注意点について説明するというものでした。
タブレットのターゲットはWindows8でした。実際にSurface Proを操作しながら解説していました。

マルチタッチ関連APIの解説の内容は自分が先日JavaFX勉強会で行ったものとほぼ同内容でしたw
タブレット向けアプリケーションを開発する上での注意点は、やはりコントロールの大きさでした。そのためにデザインにはなるべくCSSを活用し、まずはrootクラスのフォントを32px程度にするのが良いとのことでした。

結構説明が早く終わってしまったので、質問がいっぱい出ていました。自分は高解像度端末におけるスケーリングの対応について聞いたのですが、現時点では端末に合わせて自分でサイズを調整するしかないようです。でも、後で気付いたのですが、確かJavaFX8で導入されるModenaテーマはスケーリングの変化に対応していたような?

Java The Night

最後は自分も登壇したJava The Nightです。みなさん実に色んな持ちネタを披露してもらって、とても楽しめました。
驚いたのはほとんどの人がJavaFXを使っていたことです!自分が考えている以上にJavaFXJava開発者の間で認知度を上げているのかも知れません。

最後に寺田さんから発表されたJava7のAPIドキュメント日本語化のニュースは驚きました。Oracle本体を説得して日本語化にこぎつけた日本Oracleの皆様方には感謝です!


このように、聴講、発表共にとても楽しい一日を過ごすことができました。このような素晴らしい場を提供して頂いた日本Oracleの皆様には厚く御礼申し上げます。ありがとうございました。

*1:特にJavaFXサポートが入ってからは、JavaFXサポート機能がなかなか強力なので結構触るようになりました。

*2:OracleディスコンにしてしまったJavaFX Scriptオープンソース化したものです

*3:ちなみに先日のJJUG CCCでは櫻庭さんのお誘いで、Jimさんとお昼ご飯を食べに行きました。

JavaFX Advent Calendar 2012 26日目 GroovyのVetoableを使ったサンプルをJavaFXのバインディングを使って実装してみる

このエントリはJavaFX Advent Calendarの26日目のエントリとなります。前日は id:skrb / @ さんによる「JavaFX で Merry Christmas!」です。25日を過ぎちゃいました。まさか1日目と最終日を担当することになるとは。
25日分を突破するわ、海外のJavaFXエンジニアの方々に注目されてエントリを英語に翻訳するようになるわで、まさかの盛り上がりとなりましたねえ。

さて、今回のエントリですが、同じくAdvent Calendarの13日目に@さんがJavaFXでGroovyのVetoableが機能するか試してみたというエントリを書かれて、GroovyのBindable and Vetoable transformationを利用して、Model、View、Controllerを綺麗に分離する方法を紹介されていました。
ですが、JavaFX自体にもModel、View、Controllerを綺麗に分離するためのバインディングと呼ばれる機能が用意されています。このエントリではその機能を用いて上記ブログエントリのサンプルを再実装することで、バインディング機能について紹介してみたいと思います (なので先にみけさんのエントリを読んでおいてくださいね) 。

JavaFXバインディングとは?

バインディングとは、簡単に言うとあるデータと別のあるデータを結び付ける機能のことです。あるJavaBeansオブジェクトのプロパティが変化したら自動的に他のJavaBeansオブジェクトにその値を伝達できるようにします。機構の実現にはObserverパターンを利用しています。
この機構を用いることで、Modelオブジェクトのプロパティに対して、Viewに使用するコンポーネントのプロパティを「バインド」しておけば、Modelの値が変化したら自動的にViewの表示内容が変わるようになり、典型的なMVCパターンの構造を組むことができるようになりますね。

WPF等のXAMLフレームワークFlexなど、最近のGUIフレームワークは大抵このバインディング機構が備えられています。JavaScriptライブラリでもknockout.jsのようにバインディング機構を備えたライブラリが色々出てきています。

JavaFXバインディングは他のGUIフレームワークには余り見られない強力な機能が備えられています。ただし、既存のJavaBeans仕様を拡張したAPIを用いて実現しているため、記述は少々面倒だったりします。サンプル実装を通して紹介していきたいと思います。

Veotableの例をバインディングを用いて実装したサンプル

それでは実際にバインディングを用いて再実装したサンプルを示します。全ソースコードはgistにアップしています。
https://gist.github.com/4363405

まずはModelからです。

public class Person {
    // nameプロパティ
    private StringProperty name = new SimpleStringProperty();
    // 外部に公開するプロパティ
    public StringProperty nameProperty() {
        return name;
    }
    // JavaBeans仕様と互換を持たせるためのgetter、setter
    public final String getName() {
        return name.get();
    }
    public final void setName(String name) {
       this.name.set(name);
    }

    // messageプロパティ (read only)
    private ReadOnlyStringProperty message = new ReadOnlyStringPropertyBase() {
        {
            name.addListener(new InvalidationListener() {
                // nameプロパティの変化を観察し、変化したらこのプロパティも値が変化したことを通知する
                @Override
                public void invalidated(Observable o) {
                    fireValueChangedEvent();
                }
            });
        }
        @Override
        public String get() {
            String value = Person.this.getName();
            if (value.length() > 0) {
                return "Hello I'm " + value + ".";
            } else {
                return "";
            }
        }
        @Override
        public Object getBean() {
            return Person.this;
        }
        @Override
        public String getName() {
            return "message";
        }
    };
    // 外部に公開するプロパティ
    public ReadOnlyStringProperty messageProperty() {
        return message;
    }
    // JavaBeans仕様と互換を持たせるためのgetter
    public final String getMessage() {
        return message.get();
    }
}

まずは簡単な name プロパティから見てみましょう。なにやらいつものJavaBeansのプロパティとはちょっと違いますね。

    // nameプロパティ
    private StringProperty name = new SimpleStringProperty();
    // 外部に公開するプロパティ
    public StringProperty nameProperty() {
        return name;
    }
    // JavaBeans仕様と互換を持たせるためのgetter、setter
    public final String getName() {
        return name.get();
    }
    public final void setName(String name) {
       this.name.set(name);
    }

JavaFXのプロパティは **Property 型 ( ** にはラップする値のクラスが入る) でラップします。そしてこの Property 型の get/set メソッドで値のやり取りをします。
外部に公開するときは "(プロパティ名)Property()" という名前のメソッドを宣言します。また、既存のJavaBeans仕様と互換性を持たせたい場合はこのように getter/setter も用意しておきます (ただし、メソッド内では Property を通してやり取りする) 。*1
Property 型は abstract クラスなので、継承して get/set 時の処理を実装することになりますが、単純に値の受け渡しだけをする場合は Simple**Property というデフォルト実装が用意されています。
この Property オブジェクトが「バインド可能」なオブジェクトになるわけです。

続いて message プロパティです。こちらは外部から値を設定するのではなく、name プロパティの値に依存したプロパティとなっています。なので、リードオンリーなプロパティとして実装してみました。

    // messageプロパティ (read only)
    private ReadOnlyStringProperty message = new ReadOnlyStringPropertyBase() {
        {
            name.addListener(new InvalidationListener() {
                // nameプロパティの変化を観察し、変化したらこのプロパティも値が変化したことを通知する
                @Override
                public void invalidated(Observable o) {
                    fireValueChangedEvent();
                }
            });
        }
        @Override
        public String get() {
            String value = Person.this.getName();
            if (value.length() > 0) {
                return "Hello I'm " + value + ".";
            } else {
                return "";
            }
        }
        @Override
        public Object getBean() {
            return Person.this;
        }
        @Override
        public String getName() {
            return "message";
        }
    };
    // 外部に公開するプロパティ
    public ReadOnlyStringProperty messageProperty() {
        return message;
    }
    // JavaBeans仕様と互換を持たせるためのgetter
    public final String getMessage() {
        return message.get();
    }

リードオンリーなプロパティの場合、オブジェクトの内部状態に応じた値を返すことになるので、多くの場合作り込みが必要になります。JavaFXでリードオンリーなプロパティを実装する方法は大きく分けて次の2つの方法があります。

  1. abstract な ReadOnly**PropertyBase を継承して実装する。
  2. 読み書き可能な ReadOnly**Wrapper 型のオブジェクトを内部で保持し、外部にはその getReadOnlyProperty() メソッドで返すオブジェクトを公開する。

ここでは前者の方法を用いて実装してみました。バインド可能なプロパティを用意する場合、次の処理を実装する必要があります。

  • 値の読み書きと、それに付随する処理。
  • 公開する値が変化した場合、それを外部に通知する処理。

まず、初期化処理で InvalidationListenerなるものを name プロパティに対して登録していますね。これは name プロパティの値の変化を観察しています。message プロパティは name プロパティの値に依存していますから name プロパティの変化を検知した場合、このように fireValueChangedEvent() メソッドをコールして、自身に登録されたリスナに対して変更を通知します。
そして、3つのabstractメソッドを実装しています。1つめの get() が値を返す処理を実装していますね。name に何も入っていない時は空文字を返し、値が入っているときは "Hello I'm " を付けて返しています。
getBean() メソッドはプロパティのオーナーのインスタンス (ここでは Person インスタンス) を返し、getName() はプロパティ名を文字列で返します。

注目すべき点は InvalidationListener です。これは観察しているプロパティの値が変化したことだけを知らせます。これがミソです。
実はJavaFXバインディングでは遅延評価が基本となっており、実際に値が取り出された時に初めて算出するようになっています。
今回は単純に1つの値を受け渡すだけですが、実際にバインディングを使用するときは複数の値を組み合わせて算出したり、あるいは取得した値から複雑な描画処理を行ったりする可能性があります。
そこで、JavaFXでは観察されている値が変化したときは、変化したことを知らせるフラグだけを立てておき、本当にその値が必要とされるときになって、フラグが立っていたら実際の値を算出するようになっているのです。実際に値を取り出す前にデータソース側が何度変化していても、計算するのはその時の1回だけです。
私はJavaFXのこの点がすごく気に入っています。以前Flexで開発していたことがあったのですが、Flexバインディングは即時評価であるため、濫用するとパフォーマンスの低下を招きやすく、折角の機能が余り使えなくて残念な思いをしていました。

さて、ここまでのコードは長かったですが、プロパティ側の準備ができたら後は簡単です。まずはFXMLから。

<AnchorPane id="AnchorPane" prefHeight="200.0" prefWidth="320.0" xmlns:fx="http://javafx.com/fxml" fx:controller="aoetk.bindingsample.SampleController">
  <children>
    <Label layoutX="35.0" layoutY="31.0" text="Your name" />
    <TextField fx:id="txtName" layoutX="129.0" layoutY="28.0" prefWidth="177.0" />
    <Label fx:id="name" layoutX="35.0" layoutY="133.0" />
    <Label fx:id="message" layoutX="129.0" layoutY="133.0" />
  </children>
</AnchorPane>

特に変わったところはありませんね。注目して欲しいのはmike_neckさんのエントリでは TextField にイベントを定義していましたが、ここでは定義していません。
続いてControllerです。

public class SampleController implements Initializable {
    @FXML
    private TextField txtName;
    @FXML
    private Label name;
    @FXML
    private Label message;

    private Person person;

    // 初期化時に必要なバインドを行う
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        person = new Person();
        person.nameProperty().bind(txtName.textProperty());
        name.textProperty().bind(person.nameProperty());
        message.textProperty().bind(person.messageProperty());
    }
}

初期化処理を行う initialize() メソッド内でバインディングを行っています。値の変更を反映する側のプロパティの bind() メソッドを呼び出し、引数に変更元のプロパティを渡します。
まず最初に、Person#name に対して、テキストフィールドの text プロパティをバインドしています。こうすることで、テキストフィールドへの入力が name プロパティに勝手に反映されることになります。
次の2行でラベル name に対して Person#name を、ラベル message に対しては Person#message をバインドしています。
これだけでテキストフィールドの入力内容をラベルに反映する処理を実装することができました!

ちなみにControllerでバインドを行っているのは、現時点ではFXMLは単純なバインディングしか記述できないためです (FXML内で宣言した変数のみバインドが書ける) 。将来的には複雑なバインドも記述できるようにするそうです。

ともあれ、バインディングを活用すれば容易にModelとViewの値を結びつけ、しかも疎結合にできる (ModelはViewの事を一切知りません) ことがこのサンプルで分かると思います。

Property型についての補足

なお、プロパティの定義に使った **Property 型は **Expression 型を継承しています。Expressionという名前から想像できるように、様々な演算を行うためのメソッドが用意されています。これを利用することで、バインドする値を様々に加工したり、複数の値を組み合わせてバインドすることができるようになります。
これを利用すると、先ほどの例を次のようにして同じ機能を実現することが可能です。*2

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        person = new Person();
        person.nameProperty().bind(txtName.textProperty());
        name.textProperty().bind(person.nameProperty());
//        message.textProperty().bind(person.messageProperty());
        message.textProperty().bind(
                new When(person.nameProperty().isEqualTo(""))
                    .then("")
                    .otherwise(new SimpleStringProperty("Hello I'm ").concat(person.nameProperty()).concat("."))
        );
    }

流れるようなインターフェースで条件分岐やら文字列の結び付けが可能になっていることが分かります。ちょっと見辛いですけどね。
このようにある程度複雑なプレゼンテーションロジックも Expression 型の備えるメソッドで実現が可能になっています。詳しくはJavaFXのAPIドキュメントで Expression 型のメソッドを確認してみてください。

まとめ

これまで見てきたように、JavaFXには強力なバインディング機構があり、コンポーネント間の関係を疎結合にしつつ、関連する値を結びつけることが可能になります。遅延評価や流れるインターフェースを用いた演算など、なかなか強力な機能が備わっています。
難点はちょっと記述が面倒な点ですね。このあたりはJavaの表現力の限界なので致し方ないところではありますが。GroovyFXScalaFXでは言語の表現力を活かして書きやすくしてくれています。個人的にはScalaFXのバインディングの書き方は分かり易くていいなあと思っています。*3

*1:既存のJavaBeans仕様を満たす必要がない場合は省略するのもありです。

*2:最終的に実現する機能は同じですが、意味合いは大きく異なることに注意してください。この例では文字列を結合する処理がプレゼンテーション層に存在することになります。対して message プロパティを利用する例では、文字列を結合する処理はドメイン層に属することになります。

*3:自分のこのエントリでScalaFXのバインディングについて触れています。

Developing multi-touch application in JavaFX

This blog entry is the first entry in Japan JavaFX Advent Calendar 2012.

I made a presentation about multi-touch support in JavaFX at 8th Japan JavaFX workshop. I write additional information for the presentation in this entry.

Presentation material is below.

Developing multi-touch application in JavaFX from Takashi Aoe

And source code of sample application is below.
https://gist.github.com/4143183

Additional information for multi-touch API in JavaFX

JavaFX2.2 introduces API related multi-touch. I was very surprised when Developer Preview released because the description of the API has been added to JavaDoc casually without notification in release notes.

Multi-touch support is described as follows in the entry entitled "What's new in JavaFX 2.2" in The JavaFX Blog.

Multi-touch support for touch-enabled devices. As of today this is mostly relevant for desktop-class touch screen displays and touch pads, this will enable the adoption of sophisticated UIs on embedded devices running Java SE Embedded on ARM-based chipsets, such as kiosks, telemetry systems, healthcare devices, multi-function printers, monitoring systems, etc. This is a segment of the Java application market that is usually overseen by most application developers, but that is thriving.

I think main target of multi-touch support is embedded devices. It's my opinion, Oracle is probably going to be taking the initiative in the development of embedded devices user interface before Android move into the same domain.
Of course, I think it is intended for the development of iOS/Android/WindowsPhone/WindowsRT application.

As I explained in the workshop, TouchEvent handling the basic touch operation and GestureEvent handling more abstract events are added to javafx.scene.input package.
And properties to register a handler for these events are added to Scene and Node.

The following is a list of items I didn't speak for a time limit in workshop.

  • About TouchEvent
    • If you touch multiple points, eventSetId is allocated to individual Event.
    • Ctrl, Alt, Meta and Shift key combination can be detected.
    • It implements copyFor() method that copy event to other nodes.
    • In order to get the number of touch points, use getTouchCount() method.
    • TouchPoint class stores the information of touch point. It implements following methods.
      • getX()/getY() method to get the position of the touch point relative to the origin of the event source.
      • getSceneX()/getSceneY() method to get the position of the touch point relative to the origin of the Scene.
      • getScreenX()/getScreenY() method to get the absolute position of the touch point on screen.
      • There is a grabbing API to modify event target and you can switch by using grab() method ... I guess. (Sorry, I have not investigated enough.)
  • About RotateEvent
    • If the trackpad supports multi-touch, you can fire this event on the trackpad. When the event delivered on the trackpad, the mouse cursor location is used as the gesture coordinates.
  • About ScrollEvent
    • You can fire this event by mouse wheel. But only SCROLL event is delivered.
    • Use the deltaX/deltaY property to obtain the amount of scroll usually, but use the textDeltaX/textDeltaY property when the text-based component. The textDeltaXUnits()/textDeltaYUnits() method determine how to interpret the textDeltaX and textDeltaY values.
    • In order to get the number of touch points, use getTouchCount() method. You can implement multi-finger scrolling by using this.

Additonal information for sample application

I showed three samples in workshop. There are videos that had been recorded in preparation to the failure of the demonstration.

  • TouchEvent sample. Draw a circle on touch point. The circle can be moved with a touch and can be erased with a double tap.

  • Demonstration of Scroll/Rotate/ZoomEvent. I demonstrated you can move, transform the Rectangle object easily by using the values obtained from these events.

  • Demonstration of scrolling support for JavaFX control. I used ListView control and Pagination control introduced in JavaFX2.2. These controls supports scrolling operation without any coding.

I wrote compressed souece code in the presentation material because of space limitation. So I hope you check the source code uploaded to gist.
I describe below additional explanation.

  • Node class has properties scaleX/Y, rotate, translateX/Y for translation or transformation. Movement amount obtained from GestureEvent can be applied directly to these properties.
  • I set a list of font families supported by the system obtained from javafx.scene.text.Font.getFamilies() method to Pagination and ListView control.
  • The contents of the page displayed in Pagination control are Labels in VBox control as follows. Pagination control is my favorite because its API is performance-friendly.
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        fonts = Font.getFamilies();
        pagination.setPageCount(fonts.size() / FONTS_PER_PAGE);
        pagination.setPageFactory(new Callback<Integer, Node>() {
            @Override
            public Node call(Integer idx) {
                return createPage(idx);
            }
        });
        lvFonts.setItems(FXCollections.observableArrayList(fonts));
    }

    private VBox createPage(int idx) {
        VBox box = new VBox(5.0);
        int page = idx * FONTS_PER_PAGE;
        for (int i = page; i < page + FONTS_PER_PAGE; i++) {
            Label lblFont = new Label(fonts.get(i));
            box.getChildren().add(lblFont);
        }
        return box;
    }

As you see, API that enables to develop multi-touch application easily have been added in JavaFX2.2. Unfortunately, fully available environment is Windows8 devices only (furthermore, only in desktop mode), Let's use everyone!

JavaFX Advent Calendar 2012 1日目 JavaFXでのマルチタッチアプリケーション開発

JavaFX Advent Calendarの1日目を担当することになってしまった@ / id:aoe-tkです。

実は前日の第8回JavaFX勉強会にて、JavaFXでのマルチタッチプログラミングについての発表を行いました。このエントリではその内容のフォローアップをしたいと思います。
当日の発表資料は以下です。

JavaFXでマルチタッチプログラミング from Takashi Aoe

また、デモに使ったサンプルアプリケーションのコードもgistにアップしています。
https://gist.github.com/4143183

JavaFX2.2で追加されたマルチタッチ関連のAPIについての補足

発表でもお話ししたように、JavaFX2.2になってタッチ関連のAPIが追加されました。Developer Previewの時に、特にリリースノートでの通知がなく、さりげなくJavaDocに追記されていたので驚いたのを覚えています。
JavaFX2.2新機能紹介ページ (日本OracleのエンベデッドJavaチームによる日本語訳です) では次のように紹介されています。

タッチ操作可能な機器のためのマルチタッチサポート。これは現段階では主にデスクトップクラスのタッチスクリーンやタッチパッド向けですが、将来的にはARMベースのチップ上にJava SE Embeddedが搭載されているさまざまな組み込み機器に洗練されたUIを導入することを可能にします。例えばキオスク端末、テレメトリシステム、ヘルスケア機器、多機能プリンタ、監視システムなどです。これらは、多くのアプリケーション開発者にはまだあまり注目されていないJavaアプリケーションの市場セグメントですが、近年活況を呈しつつあります。

当日の発表でも少し触れましたが、主に組み込み機器をメインターゲットにしているように見受けられます。こういったセグメントにもこれからAndroidが進出してくるでしょうから、その前にしっかり押さえておこうとOracleは考えているのでしょう。
もちろんiOSWindows8/RT/Phone、Android向けの開発も視野には入れているとは思いますが。

さて、追加されたAPIの内容についてですが、発表でも説明したように、javafx.scene.input パッケージに基本的なタッチ操作を扱う TouchEvent と、タッチ操作を組み合わせたより抽象度の高い GestureEvent というイベントが追加されています。
そして、Scene 及び Node にこれらイベントに対するハンドラを登録するためのプロパティが追加されています。

以下に時間の関係で当日の発表で話せなかったことを列挙します。

  • TouchEvent について
    • 複数点タッチされた場合、個々のEventには eventSetId が割り振られます。
    • control、alt、meta、shiftキーとのコンビネーションも検出可能です。(isAltDown() とか isShiftDown() といったメソッドがある)
    • 他のノードにイベントをコピーする copyFor() というメソッドがあります。
    • 何点同時にタッチされているかは getTouchCount() メソッドで取得できます。
    • タッチポイントの情報は TouchPoint クラスに格納されますが、TouchPoint には次のようなAPIが用意されています。
      • getX()/getY() はタッチの対象となる Node オブジェクト上の座標を、getSceneX()/getSceneY() は Scene 上の座標を、getScreenX()/getScreenY() は画面上の絶対座標を取得でします。
      • タッチポイントのイベントターゲットとなる Node オブジェクトを切り替えることのできる grabbing API というものがあり、grab() メソッドで切り替えることができる...らしいです。(ごめんなさい、まだ調べ切れていません)
  • RotateEvent について
    • マルチタッチをサポートしているトラックパッドならば、トラックパッドでもこのイベントを起こすことができます。トラックパッドで起こした場合はマウスカーソルの位置が基準になります。
  • ScrollEvent について
    • じつはマウスホイールによるスクロールでもこのイベントは発生します。ただし、マウスホイールの場合は SCROLL タイプのイベントのみ発生します。
    • 移動量については基本的に deltaX/Y で見ますが、テキスト系コンポーネントの場合は textDeltaX/Y でも取得可能なようです。単位は行単位とページ単位があるようで、getTextDeltaUnits() でどちらの単位なのかを取得できます。
    • getTouchCount() でタッチ数を取得できます。これを使うことで2本指スクロールや3本指スクロールが実現できます。

デモアプリケーションについての補足

デモでは次の3つのサンプルをお見せしました。デモが失敗したときに備えて録画しておいた動画がありますので、それも一緒にアップしておきます。

  • TouchEvent を使い、タッチされたポイントに対してサークルを描画するデモ。描画したサークルはタッチで移動でき、ダブルタップで消去可能。

  • GestureEvent のうち、Scroll/Rotate/ZoomEvent を利用したデモ。描画した Rectangle に対して、これらイベントから取得した値を使って、簡単に移動、変形が可能であることを示しました。

  • JavaFXのコントロールのスクロール対応のデモ。ListView コントロールとJavaFX2.2になって追加された Pagination コントロールを使いました。これらは何もしなくてもスクロール操作に対応しています。

スライドではスペースの都合上、コードをかなり圧縮して載せていました。上のgistにアップしたコードの方を良く確認してもらえたらと思います。
以下にちょっと補足を。

  • Node には移動や変形を行うための scaleX/YrotatetranslateX/Y といったプロパティがあります。GestureEvent で取得できる移動量はこれらプロパティにそのまま適用できるようになっています。
  • Pagination コントロールや ListView コントロールには、javafx.scene.text.Font.getFamilies() メソッドを使って取得した、システムがサポートしているフォント名の一覧を渡しています。
  • Pagination コントロールに表示するページの内容は次のように VBox を作って、その中にラベルを並べています。Pagination コントロールはパフォーマンスに考慮した作り方ができるようなAPIになっていて、個人的に気に入っています。
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        fonts = Font.getFamilies();
        pagination.setPageCount(fonts.size() / FONTS_PER_PAGE);
        pagination.setPageFactory(new Callback<Integer, Node>() {
            @Override
            public Node call(Integer idx) {
                return createPage(idx);
            }
        });
        lvFonts.setItems(FXCollections.observableArrayList(fonts));
    }

    private VBox createPage(int idx) {
        VBox box = new VBox(5.0);
        int page = idx * FONTS_PER_PAGE;
        for (int i = page; i < page + FONTS_PER_PAGE; i++) {
            Label lblFont = new Label(fonts.get(i));
            box.getChildren().add(lblFont);
        }
        return box;
    }

このようにJavaFX2.2ではマルチタッチを容易に開発可能なAPIが追加されています。今のところまともに使える環境はWindows8くらい (しかもデスクトップモード) なのがちょっと悲しいですが、みなさんもどんどん使ってみてください。

第7回JavaFX勉強会でLTしてきました

ちょっと遅くなりましたが先日行われた第7回JavaFX勉強会に参加し、LTもしてきましたので、こちらで軽く報告します。

自分の発表資料は以下の通りです。
http://www.slideshare.net/takashiaoe/macjavafx-at-7javafx-13518044

発表内容はMacでのJavaFXの開発や配備についてのお話しです。ちょっとMacユーザーならではの自虐ネタも交えてw
内容は結構絞ったつもりですが、それでもLTの5分間となると駆け足でしゃべるしかないですね。もう少し時間があればJARバンドラーのデモをやりたいと思っていました。
JARバンドラーについては後日このブログでサポートするエントリでも書こうかな。
発表でも最後に話しましたが、MacでもJavaFXの開発環境はほぼ整いました。と言うことでMacユーザーの皆様もJavaFXでの開発を楽しんでくださいね。
JDK7がLionしかサポートしていないのはつらいですが、Macの世界はいつもこんな感じですからねえ...。iCloudとかもSnow Leopard以前はガン無視ですし。

では他の方々の発表についてもそれぞれ軽くコメントを。

  • 片貝さんのNetBeansについての発表 (発表資料)
    • NetBeans7.2ではSceneBuilderとの連携強化や、CSSの補完が効くようになったり (個人的にはこれは嬉しい!) 、とJavaFXの開発環境としての強化が順調に図られているようです。あとはFXMLの補完が効くようになってくれれば...。
    • 地味にSwingベースのJavaFXアプリケーションプロジェクトが作成できるようになっているのは嬉しいですね。今後JavaFXが最も良く使われるのがSwingのリプレースだと思いますが、印刷などまだまだAWT/Swingをカバーできていないところが多いので、これの重要性は高いと思っています。
    • みんなここぞとばかりにNetBeansへのリクエストを出してましたw 自分も懇親会でいくつかリクエストしたような。
  • 櫻庭さんのSceneBuilderについての発表 (発表資料)
    • SceneBuilderについて、デモを中心にした説明でした。相変わらずJavaFXで作った資料のクオリティが高い! 驚いていた参加者も多かったように見えます。
    • NetBeansとSceneBuilderの分担が少し中途半端とのことですが、これはMaciOSの開発でのXCodeとInterfaceBuilderの分担を真似たからこうなっているのかなと個人的には思っています。
    • 質問が沢山飛んでいました。そのやりとりの中でカスタムコンポーネントのパレットへの登録ができない、と言うの話が出てきましたが、これは確かに早く実装して欲しいですね。
      • 質問の中に、コンポーネントの整列はないか? と言うのがありましたが、コンポーネントを選択してGridPaneを適用することはできるので、これで代替できそうだと後で気付きました。
      • それからSceneBuilderは結構すいすい動きますよー。機能を純粋にFXMLの編集だけに絞っているというシンプルな作りだからというのもあるでしょうが。
  • mike_neckさんのFx-JS-JUnitについてのLT (発表資料)
    • ニャル子さんネタ全開の怒濤のスライド! これはやられましたw トークも軽妙で場の雰囲気持って行っちゃいましたね。
    • でも中身は普通に数十分掛けてやるようなとても濃い内容です。JavaFXのWebEngineを使ってJavaScriptのテストに活用するという発想は面白いと思います。期待しています。
  • taiz77さんの画面遷移ライブラリのLT
    • 大学院でJavaFXを研究の題材にされているそうです!
    • JavaFXのアプリケーションフレームワークを考えておられるようで、現時点ではJavaFXに対するフレームワークというのは発表でも紹介されていたJFX Flowくらいしか見当たらないので。こういう取り組みは貴重だと思っています。
    • ただ、タイムアップでそのフレームワーク自体の紹介までたどり着かなかったのが残念でした。
  • kimukou2628さんのGriffonについてのLT (発表資料)
    • デスクトップGUI向けGrailsのような位置付けを目指しているフレームワークであるGriffonについての紹介でした。
    • 先にJavaFXではまだフレームワークらしきものはない、と書きましたが、このGriffonも有望だなーと思いました。FXMLとの連携も考えているのがいいですね。
  • 寺田さんのJavaFXエンタープライズアプリケーションについてのLT (発表資料)
    • 何とApplication Client Containerを使ってJavaFXとサーバーのEJBを連携させる方法についての発表でした。ある意味今後JavaFXが最も使われそうな用途のようにも思えます。
    • 実は自分は過去のSI案件でSwingアプリケーションとサーバーのEJBを直接RMIで接続する形態の開発に関わったことがあります。ですがそれは古のJ2EE1.3の時代の話。JavaEE6でのEJBへのリモート接続についての情報はとても貴重だと思いました。
      • リモートEJBのインジェクション対象のフィールドはstaticである必要があったんですねえ...。

このような感じで他の方々の発表も内容が多岐にわたり、充実していました。発表資料へのリンクを張っておいたので、是非見て頂ければと。

回を重ねるごとに参加人数が増えてきているのに驚きです。Swingをやっていた方はもちろん、Flexのような他のRIAの世界から来た感じの方とかもいましたねえ。
懇親会も参加者が多くて、色んな方と交流ができて楽しかったです。どんどん盛り上がってきて嬉しい限りです。