「*[JavaFX]」の検索結果を表示しています
2014-03-31 00:14

JavaFXのWindows環境におけるHiDPI対応について調べたことのメモ

先日のVAIO Tap 11レポートのエントリで、こんなことを書いていました。

ただ、高 DPI スケーリングに対応していないアプリケーションが多いことが分かりました。
(中略)
NetBeans はデフォルトだとコンソールの文字が豆粒のようになってしまい、とても悲しかったです (タスクバーの大きさに対するコンソールの文字の大きさに着目) 。

そのうち JavaFX で高 DPI に対応するにはどうすればいいか、調べてみるつもりです。

というでちょっと調べてみましたが、まだいい対応策が見つかっていないので、以下に現在分かっている点だけをとりあえず列挙します。

というわけで現状 Windows 環境では HiDPI に対応したアプリケーションを作るのは容易ではない感じです。
一番やりやすいワークアラウンドは、CSS のピクセル指定部分をテンプレート化しておいて、まとめて置き換える方法ですかね。もしかしたら単位に em を使えばいいのかな?

とにもかくにも JavaFX が Windows でも Device Independent Pixel (DIP) に対応してくれるのが一番なんですけどねえ。
JIRA にもこのようなチケットが上がっていますが、Windows の世界でも高解像度端末が増えてきていますし、早く対処してもらいたいところです。

現在分かっていることはこんなところです。もう少し調べて何か分かったらまたエントリをアップします。

2013-12-07 00:55

ListViewやTableViewのセルをカスタマイズする方法 (JavaFX Advent Calendar2013 7日目)

このエントリは JavaFX Advent Calendar 2013 の 7 日目のエントリです。前日は蓮沼 (@) さんによる、「e(fx)clipseで作るJavaFXアプリケーション」でした。

はじめに

Twitter やブログなどで JavaFX に関するエントリを見ていると、ListViewTableView のセルの表示をカスタマイズする方法がよく分からずに苦戦している人をよく見かけます。
このようなリスト系コンポーネントの表示をカスタマイズする方法は、実はどの GUI ツールキットでも大体やり方が似ているのですが、HTML を使った UI の開発ばかりをやっている人には馴染みがないかも知れません。
そこで、少し地味ではありますがこの方法について解説します。また、セルのカスタマイズをする際には性能面で注意が必要なポイントがあるので、それについても触れたいと思います。
なお、ListView も TableView も基本的には同じような方法でセルのカスタマイズが行えるので、以降の説明は ListView に絞って行います。

ListView の仕組み

リストのセルの表示をカスタマイズする方法を理解するためには、まずは ListView の仕組みを知っておいた方がいいと思うので、まずはそこから説明します。
まずは次のようなシンプルなリストを見てみましょう。

f:id:aoe-tk:20131202010914p:image

このリストは 500 件のアイテムを表示しています。大量にアイテムがあるので、大半が隠れていて、スクロールを行う必要があります。
一見すると隠れている領域にもセルのインスタンスがあるかのように思えますね。
ところが、シーングラフの状態を確認することができる、Scenic View を使って見てみると...。

f:id:aoe-tk:20131202010950p:image

なんと表示されている範囲のインスタンスしか生成されていないのです! (ListCell インスタンスの数に注目してください)
ListView はこのように表示されている範囲のセルのインスタンスだけを作り、スクロールが発生するとセルの表示をスクロール位置に対応したデータに合わせて切り替えることで、あたかも隠れた範囲から新しいセルが入ってきたかのように見せかけているのです。
こうすることで、大量のデータを表示しても性能が落ちないようにしているのです。

セルのカスタマイズ方法

それではセルのカスタマイズ方法について説明しましょう。セルの表示をカスタマイズするには次の 2 つのことを行う必要があります。

  1. カスタムの Cell クラスを作り、表示したいレイアウトを実装する
  2. ListView に対して上で作ったカスタムの Cell クラスを使うように CellFactory を設定する

具体例を示しながら説明していきます。今、自分用にソーシャルブックマークサービスの Diigo のビューアを JavaFX で作ろうと考えているのですが、それのブックマークリスト部分を先に作ってみることにします。
セルの表示を次のようにすることを考えてみます。
f:id:aoe-tk:20131205011154p:image
上から順にタイトル、コメント、タグを表示します。タグはハイパーリンクとして横に並べるようにします。

まずはリストのデータソースとなるモデルを用意します。次のような JavaFX 形式の JavaBean として作ります。

/**
 * ブックマークを表すモデル.
 */
public class BookmarkModel {
    private StringProperty title = new SimpleStringProperty();
    private StringProperty comment = new SimpleStringProperty();
    private ObservableList<String> tagList = FXCollections.observableArrayList();

    /**
     * コンストラクタ.
     * @param title タイトル
     * @param comment コメント
     * @param tagList タグのリスト
     */
    public BookmarkModel(String title, String comment, List<String> tagList) {
        this.title.set(title);
        this.comment.set(comment);
        this.tagList.addAll(tagList);
    }

    public StringProperty titleProperty() {
        return title;
    }

    public StringProperty commentProperty() {
        return comment;
    }

    public ObservableList<String> getTagList() {
        return tagList;
    }
}

このオブジェクトを ObservableList にくるんで ListView にデータとして渡すことになります。

次はカスタムの Cell クラスを作りましょう。

public class BookmarkCell extends ListCell<BookmarkModel> {
    private VBox cellContainer;
    private Text txtTitle;
    private Text txtComment;
    private HBox tagContainer;
    private boolean bound = false;

    public BookmarkCell() { // (1)
        initComponent();
        initStyle();
    }

    private void initStyle() {
        txtTitle.setFont(new Font("System Bold", 18.0));
    }

    private void initComponent() { // (2)
        cellContainer = new VBox(5);
        txtTitle = new Text();
        VBox.setVgrow(txtTitle, Priority.NEVER);
        txtComment = new Text();
        VBox.setVgrow(txtComment, Priority.ALWAYS);
        tagContainer = new HBox(); // (3)
        VBox.setVgrow(tagContainer, Priority.NEVER);
        cellContainer.getChildren().addAll(txtTitle, txtComment, tagContainer);
    }

    @Override
    protected void updateItem(BookmarkModel bookmarkModel, boolean empty) { // (4)
        super.updateItem(bookmarkModel, empty);
        if (!bound) { // (5)
            txtTitle.wrappingWidthProperty().bind(getListView().widthProperty().subtract(25));
            txtComment.wrappingWidthProperty().bind(getListView().widthProperty().subtract(25));
            bound = true;
        }
        if (empty) {
            setText(null);
            setGraphic(null);
        } else { // (6)
            txtTitle.setText(bookmarkModel.titleProperty().get());
            txtComment.setText(bookmarkModel.commentProperty().get());
            tagContainer.getChildren().clear();
            List<String> tags = bookmarkModel.getTagList();
            for (String tagName : tags) {
                tagContainer.getChildren().add(createTagLink(tagName));
            }
            setGraphic(cellContainer);
        }
    }

    private Hyperlink createTagLink(String tagName) {
        Hyperlink hyperlink = new Hyperlink(tagName);
        hyperlink.setTextFill(Color.BLUE);
        return hyperlink;
    }
}

カスタムの Cell は javafx.scene.control.Cell を継承して実装しますが、ListView 向けには javafx.scene.control.ListCell という基底クラスが用意されているので、通常はこれを継承して実装します。 (TableView の場合は javafx.scene.control.TableCell を継承します)
型パラーメータには ListView のデータソースとなるクラスを指定します。今回の場合、先ほど上で作った BookmakrModel を指定することになります。

カスタムの Cell では updateItem() メソッドをオーバーライドします。
このメソッドは初期表示やスクロール時など、セルの表示を更新する必要があるときにコールバックとして呼び出されます。
第1引数には、このセルが表示を担当することになったデータソース (この例では BookmarkModel のインスタンス) が渡されます。第2引数にはこのセルが表示するデータが空であるかが渡されます。(空の場合に true)
第1引数に渡されたデータを使って、セルの表示内容を更新することになります。Cell クラスは Label や Button のスーパークラスである javafx.scene.contorol.Labeled を継承しており、setText() メソッドを使ってテキストを、setGraphic() メソッドを使って任意のグラフィックを表示するために設定することができます。
よって、任意のコンテンツをセルに表示したい場合、表示する Node を作って、この updateItem() メソッド内において、setGraphic() メソッドの引数にその Node を渡せばいいわけです。

ここで1つ注意点があります。それは、updateItem() メソッド内での Node オブジェクトの生成は極力避ける、ということです。
updateItem() メソッドはスクロールを行った場合など、高頻度で呼び出される可能性があります。そのため、このメソッド内で Node オブジェクトの生成を行うと、その負荷はかなりのものとなります。
従って、可能な限り Node オブジェクトの生成は Cell クラスの初期化時に行い、updateItem() メソッドではプロパティやレイアウトの変更にとどめるようにします。

以上を踏まえて今回の実装例について解説します。
まず、Cell クラスのコンストラクタで初期化可能な Node のインスタンスを全て作っています。(1)
実際に Node の生成を行っているのは initComponent() メソッドです。(2)
セルでは上からブックマークタイトル、コメント、そしてタグのリストを並べるのですが、これは VBox を作って配置します。
ブックマークタイトル、コメントの表示には javafx.scene.text.Text を使います。
タグについては BookmarkModel が保持するタグの数に応じて動的に変化するので、これは updateItem() メソッドで生成するようにしていますが、そのタグを水平に並べるための HBox だけをここでは生成して、コンテナとなる VBox に追加しています。(3)

続いて updateItem() メソッドの実装です。(4)
まず、ブックマークタイトルやコメントのテキストがセルの幅に合わせて折り返されるように、親となる ListView の width プロパティと、それぞれの wrappingWidth プロパティをバインドしています。(5)
コンストラクタの中で行わず、最初に updateItem() メソッドが呼び出されたときにこのバインドを実行している理由は、コンストラクタが呼び出される時点ではまだセルが配置される ListView と結びつけられていないからです。
そして、第2引数が false の場合に、引数として渡される BookMarkModel オブジェクトから値を取り出して、Text や HyperLink オブジェクトに設定しています。(6)
今回は少し手を抜いて、updateItem() メソッドの中で HyperLink を生成していますが、さらに性能を重視するのなら、コンストラクタである程度の数のインスタンスを作ってキャッシュしておいた方が良いでしょうね。

それでは、このカスタムのセルを使ってみることにします。まずは FXML から。

<BorderPane id="BorderPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="460.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="aoetk.SampleListViewController">
  <center>
    <ListView fx:id="listView" prefHeight="200.0" prefWidth="200.0" />
  </center>
</BorderPane>

BorderPane の真ん中に ListView を貼っているだけです。続いて Controller の実装です。

public class SampleListViewController implements Initializable {
    public ListView<BookmarkModel> listView;

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        listView.setCellFactory(new Callback<ListView<BookmarkModel>, ListCell<BookmarkModel>>() { // (1)
            @Override
            public ListCell<BookmarkModel> call(ListView<BookmarkModel> listView) {
                return new BookmarkCell();
            }
        });
        ObservableList<BookmarkModel> bookmarkModels = createBookmarkModels();
        listView.setItems(bookmarkModels); // (2)
    }

    private ObservableList<BookmarkModel> createBookmarkModels() { // (3)
        final String longComment = "長いコメントのサンプルです。長いコメントのサンプルです。長いコメントのサンプルです。"
                + "長いコメントのサンプルです。長いコメントのサンプルです。長いコメントのサンプルです。長いコメントのサンプルです。"
                + "長いコメントのサンプルです。長いコメントのサンプルです。長いコメントのサンプルです。長いコメントのサンプルです。";
        final String normalComment = "普通の長さのコメント。";
        ObservableList<BookmarkModel> bookmarkModels = FXCollections.observableArrayList();
        for (int i = 0; i < 500; i++) {
            String comment = "";
            if (i % 3 == 0) {
                comment = longComment;
            } else if (i % 3 == 1) {
                comment = normalComment;
            }
            BookmarkModel model = new BookmarkModel(
                    "ブックマークタイトル" + i, comment, Arrays.asList("tag1", "tag2", "tag3"));
            bookmarkModels.add(model);
        }
        return bookmarkModels;
    }
}

initialize() メソッドで ListView にセルの設定を行っています。ListView#setCellFactory() メソッドを使い、セルを生成する CellFactory を設定しています。(1)
CellFactory は、javafx.util.Callback インターフェースを実装したクラスとなります。call() メソッドで、使いたい Cell クラスのインスタンスを返すようにします。
後はデータの設定です。createBookmarkModels() メソッドにて、500個の BookmarkModel のインスタンスを生成して (3) 、そのリストを ListView のアイテムとして設定しています。(2)

これを実行すると次のようにカスタマイズしたセルを使った ListView が表示されます。

f:id:aoe-tk:20131207003047p:image

いかがでしたでしょうか。ちょっと回りくどいと感じる方もいるかもしれません。ですが、このような作りになっていることでモデルとビューが綺麗に分離できますし、リストの項目数が多くても描画性能へ与える影響を小さくすることができるというメリットがあります。
また上でも述べたように、JavaFX に限らず、GUI アプリケーションではリスト系コンポーネントの表示をカスタマイズしようとしたら、大体これと同じような方法を採ります。なので他の GUI プラットフォームで開発するときにも同じ考え方が通用するので、是非この機会に覚えてもらえたらと。

明日は@さんの予定です。

追記

ソースコード全体は gist にアップしています。ご参照ください。
https://gist.github.com/aoetk/7827455

2013-10-27 22:21

JavaOne報告会でJavaFXについての発表&LTを行ってきました

10/19 に実施されたJavaOne 2013 サンフランシスコ報告会 Tokyoにて、JavaFXのアップデートについての発表とLTを行ってきました。

当日のJavaFXアップデートの資料は以下の通りです。



JavaOne2013報告会 JavaFX Update from Takashi Aoe

JavaOne初参加の身でありながら、例年は櫻庭さんが担当されているポジションを引き継ぐことになったので、とても緊張しました。
自分の前に寺田さん、大山さん、櫻庭さんが発表がありました。3人の発表で場が大盛り上がりした状態で自分の発表に入ったのですが、自分の発表になると雰囲気がしーんとした感じになってしまったので、「うわ、これは失敗かなあ...」とショックを受けたのですが、後から聞いた話だと結構皆さんそれなりに楽しんで聞いて頂けたようで、ほっとしています。
そうは言ってもこのような大きな場で長丁場の発表をするのにはまだまだ修行が必要だと言うことがよーく分かりました。精進します。

本編のJavaFXアップデートに続いて、LTでもお話しをしてきました。こちらはJavaOneではちょっと珍しい感じのHadoopのセッションについてです。
JavaFXの方は真面目な感じで行ったので、こちらはちょっとくだけた感じにしました。
発表資料は次の通りです。



JavaOne2013報告会 LT資料 Hadoopの話を聞いてきた from Takashi Aoe

今のところSlideShareでのページビューはLTの方がちょっと上ですね。何だかんだ言ってHadoopは注目ネタですかね。

さて、ブログの方のJavaOne報告もまだ半分ほど残っていますね。こちらも早めにアップします。(^^;;

2013-07-22 00:34

意外に優秀なJavaFX WebViewのHTML5フォーム対応

久々のポストです。(そういやFxGlassFishMonitorの解説も1回書いただけでさぼってるなー。こちらも早くまとめねば)

今、仕事で完全に社内向けの管理用Webアプリケーションを作っているのですが、どうせ完全に内部向けなら古いブラウザなんか気にせずHTML5で追加されたフォームをばんばん使っちゃえ♪ って感じで作っています。
で、ふと、「JavaFXのWebViewはWebKitベースなんだけど対応状況はどうなんだろう?」と思って調べてみたら意外に対応状況が優秀だったことがわかりました。さすがにChromeには負けますが、Safariよりは対応が進んでいたりします。と言うわけでその点についてまとめてみます。

以下のコードを表示してみることにします。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>HTML5フォームテスト</title>
    <style>
:invalid {
  border-color: #e88;
  -webkit-box-shadow: 0 0 5px rgba(255, 0, 0, .8);
  -moz-box-shadow: 0 0 5px rbba(255, 0, 0, .8);
  -o-box-shadow: 0 0 5px rbba(255, 0, 0, .8);
  -ms-box-shadow: 0 0 5px rbba(255, 0, 0, .8);
  box-shadow:0 0 5px rgba(255, 0, 0, .8);
}
    </style>
  </head>
  <body>
    <form action="" method="GET">
      <p>
        number: <input type="number" min="0" max="100" name="number">
      </p>
      <p>
        date: <input type="date" name="date">
      </p>
      <p>
        search: <input type="search" name="search">
      </p>
      <p>
        url: <input type="url" name="url">
      </p>
      <p>
        email: <input type="email" name="email">
      </p>
      <p>
        month: <input type="month" name="month">
      </p>
      <p>
        range: <input type="range" min="0" max="100" name="range">
      </p>
      <p>
        color: <input type="color" name="color">
      <p>
      <p>
        meter: <meter value="60" min="0" max="100"></meter>
      </p>
      <p>
        progress: <progress max="100">進行中だよ</progress>
      </p>
      <input type="submit" value="submit"name="submit">
    </form>
  </body>
</html>

HTML5で追加されたフォームのうち代表的なものを記述しています。invalid 疑似クラスを指定しており、指定したフォームに対して妥当でない値が入力された場合にはボーダーの色を変更するようにしています。ブラウザが対応していれば、妥当でない値を入れると色が変わるようになります。

まず、これをChrome (バージョン 28.0.1500.71) で表示すると次のようになります。
f:id:aoe-tk:20130721231344p:image
今回指定した全てのフォームに対応しています。number や date、month、range、color に対しては入力補助のコンポーネントが表示されており、url や email に対しては、妥当でない値を入れるとCSSでの指定通りにボーダーが赤くなっていることが分かります。<meter> や <progress> にもちゃんと対応していますね。
date については次のようなカレンダーピッカーを出すことができます。
f:id:aoe-tk:20130721231651p:image

Firefox (22.0) では次のように、url、email、meter、progress には対応していますが、number、date、month、range、color には対応しておらず、入力補助コンポーネントも出ません。
f:id:aoe-tk:20130721231917p:image

Safari (6.0.5) についてもMac版は意外と対応が遅れており、date、month、color が対応していません。iOS版はもう少し対応が進んでおり、date や month ではあのドラム型の入力コンポーネントが出てきます。
f:id:aoe-tk:20130721232203p:image

Opera (12.15 *1 ) は一番対応が早かったブラウザで、今回指定した全てのフォームに対応しています。
f:id:aoe-tk:20130721232658p:image

では、JavaFXではどうでしょうか。バージョン2.2のWebViewを使って表示した結果は次の通りです。
f:id:aoe-tk:20130721233045p:image
何と、search と color 以外は全て対応しているのです!number や date、month は一見対応していないように見えますが、実はフォーカスしてカーソルキーを上下すると数値や日付の値を入力できるようになっています。(画像では分からないですが)

で、この件についてTwitterでさくらばさんにさらに次のような情報をもらいました。

と言うわけでJDK8 Early Access (1.8.0-ea-b99) をダウンロードして、JavaFX8のWebViewでも見てみることにしました。
結果は次の通りです。
f:id:aoe-tk:20130722002154p:image
何とdate、month、colorでピッカーが用意されるようになりました!でも、このバージョン (8 Build b99) ではバグがあるのかクリックしても何も反応しませんでした。(´;ω;`)ブワッ
まあ、正式版が出るまでには対応されるでしょうw

ちなみにJavaFX8では DatePicker コンポーネントが追加されています。こんな感じです。
f:id:aoe-tk:20130722002626p:image
バグが直るときっとWebViewでもこんなピッカーが表示されるようになるのでしょう。

というわけでJavaFX WebViewのHTML5フォーム対応状況についてでした。

おまけ

今回、JDK8でJavaFXアプリを作るためにNetBeans7.4betaをダウンロードして使ってみたのですが、NetBeans7.4ではMavenベースのJavaFXプロジェクトを作れるようになっていました。これはうれしい!
f:id:aoe-tk:20130722002627p:image

(追記)
さすがにIEの結果がないのはちょっとあれだと思ったので、VMを立ち上げてWin8上のIE10での結果も撮って追加しました。
f:id:aoe-tk:20130722005420p:image
IE10は number、url、email、range、progress に対応していました。ただし number は妥当性検証のみで入力補助はありません。range の表示がWin8らしいですね。

*1:Blinkになる前のバージョンです

2013-05-26 23:16

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

はじめに

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

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

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

今回は懐かしのSwingSet2にあったダイアログを作ってみましょう。以下に示すものです。
f:id:aoe-tk:20130526212404p:image
Yesと答えると「コンピュータの前にいないで外へ出ろ」と説教されるやつですw
YesとNoについてはそれぞれ新たなメッセージダイアログが次に開き、Cancelが選ばれたら何も表示せずにそのまま閉じます。
Yesがデフォルトボタンに設定されていて、キーボードでEnterを押すとYesが選択されます。また、Escを押すとキャンセル扱いになり、そのまま閉じます。

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

ではJavaFXでこれを作ってみましょう。
先にも述べたように、Scene Builder1.1 にはダイアログのひな形が用意されています*1。メニューから [ファイル] - [テンプレートから新規作成] - [アラートダイアログ] を選択します。
f:id:aoe-tk:20130526213347p:image
すると次のようにダイアログのひな形になるFXMLが生成されます。
f:id:aoe-tk:20130526213529p:image:w800
ちなみにメニューのスクリーンショットを見てお気づきになったかと思いますが、CSSやリソースバンドルを一緒に生成してくれるメニューもあります。
これをベースに自分の好みに合わせてちょいちょいといじっていけばいいわけです。今回は次のように仕上げました。
f:id:aoe-tk:20130526214037p:image
具体的に行った変更点は次の通りです。

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" />
        
      </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>

同じようにして、メッセージダイアログのデザインも作成します。
f:id:aoe-tk:20130526214719p:image

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を示します)

このコードを実行すると次のように動きます。
f:id:aoe-tk:20130526230612p:image:w1000

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

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

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

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

次へ
aoe-tk
aoe-tk