JavaFX Hands on Lab

テーブルと組み込みブラウザ

ハンズオンラボの後半では、もう少し複雑なアプリケーションを作ってみましょう。

ここでは、GUI の部品の中では使用頻度の高いテーブルと、JavaFX の特徴的な機能の 1 つである組み込みブラウザを組み合わせたアプリケーションを作成していきます。

何を作るかというと、ブックマークです。ブックマークをテーブルで表示し、ブックマークで指定した Web サイトをブラウザで表示するようにします。

アプリケーションの完成予想図を次に示します。

BookmarkTable

 

まず、プロジェクトを作成しておきましょう。ここではプロジェクト名を BookmarkTable とします。

メインクラスが BookmarkTable クラス、FXML が BookmarkView.fxml、コントローラクラスを BookmarkViewController クラスとしました。

NetBeans で作成した場合のプロジェクトを以下に示しておきます。

また、完成した BookmarkTable プロジェクトは GitHub からクローンできます。

BookmarkTable プロジェクト https://github.com/skrb/BookmarkTable

テーブル

JavaFX でテーブルは TableView クラスで表します。

TableView クラスはカラムを表す TableColumn クラスを複数保持しています。これは Swing の JTable クラスと同じような構成です。

しかし、Swing の JTable クラスが、表示するデータを TableModel インタフェースを実装したクラスで表すのに対し、TableView クラスでは Java Beans であれば大丈夫だということです。

ただし、JavaFX で拡張した Java Beans で表す必要があります。

まずは、TableView を Scene Builder を使用して構成しましょう。

このアプリケーションでは、ルートコンテナとして BorderPane クラスを使用しました。BorderPane クラスは上、下、左、右、中央の 5 箇所にノードを貼ることのできるコンテナです。それぞれ top, bottom, left, right, center プロパティで表します。

ここでは top に TableView を貼ります。

Hello, World! の場合と同様に、IDE でルートコンテナを BorderPane に変更しておきます。以下に BookmarkView.fxml を示します。ただし、import は省略しています。

<BorderPane prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml" fx:controller="bookmarktable.BookmarkViewController">
</BorderPane>

では、Scene Builder を起動しましょう。

  1. ブラウザを開くにはルートコンテナのサイズが小さいの、まずサイズを調整します。BorderPane を選択して、右側のレイアウトから Pref Width と Pref Height を指定します。ここでは Pref Width を 800、Pref Height を 600 としました
    サイズの調整
  2. 左側のライブラリからブラウザを TableView を選択し、BorderPane の top にドラッグ & ドロップします。マウスカーソルがある領域が濃い紫に変化するので、どこにドロップするか容易に判断できるはずです
    TableView をドラッグ & ドロップ
  3. ドロップしたテーブルには、すでに 2 つの [列X] という カラムが設定されています。そこで、このカラムの Text を変更します。左側のカラムを Site、右側のカラムを URL とします
    カラムの Text を変更
  4. カラムのサイズも変更します。JavaFX では Swing と同様に、ノードのサイズを直接変更するのではなく、コンテナがレイアウトに応じてサイズを決定します。ノードは最適なサイズ、最小・最大サイズなどを設定しておきます。ここでは、幅が 800 なので、最適幅を示す prefWidth を Site が 290、URL が 490 としました。足して 800 にしていないのは垂直方向のスクロールバーが必要な場合、スクロールバーの表示領域を設けておくためです
    カラムのサイズ変更
  5. ここで、FXML のプレビューをしてみましょう。プレビューはメニューバーの [プレビュー] - [ウィンドウにプレビューを表示] で行います
    プレビュー
  6. 表示するデータがないのですが、ダミーのデータを表示することができます。メニューバーの [表示] - [サンプルデータの表示] を選択します。すると、編集ペインでもデータが表示されます。同様にプレビューでもデータが表示されます。しかし、FXML にはデータは含まれません
    サンプルデータの表示
  7. コントローラクラスから TableView や TableColumn にアクセスできるように fx:id を指定します。TableView を table、Site カラムを siteColumn、URL カラムを urlColumn にしました

 

ここまでの段階の BookmarkView.fxml を以下に示します。

<BorderPane prefHeight="600.0" prefWidth="800.0" 
            xmlns:fx="http://javafx.com/fxml/1" 
            xmlns="http://javafx.com/javafx/2.2"
            fx:controller="bookmarktable.BookmarkViewController">
  <top>
    <TableView prefHeight="200.0" prefWidth="200.0">
      <columns>
        <TableColumn minWidth="50.0" prefWidth="290.0" text="Site" />
        <TableColumn minWidth="100.0" prefWidth="490.0" text="URL" />
      </columns>
    </TableView>
  </top>
</BorderPane>

ここまでの段階の BookmarkView.fxml を以下に示します。

JavaFX での Java Beans

JavaFX では Java Beans を様々なところで使用します。JavaFX でも Java Beans の基本的な部分は一緒です。

Java Beans であるためには、デフォルトコンストラクタを持つことや、プロパティに対しセッター/ゲッターを持つことなどが条件になります。

JavaFX では Java Beans が保持するプロパティを独自のクラスで保持させるようにしています。それが JavaFX のプロパティです。

JavaFX ではバインドという機能を使用して、複数のプロパティの値を自動的に同期させることができます。このようなことを行うために、Java のプリミティブや文字列、オブジェクトのためのプロパティクラスが提供されています。

たとえば、xy 座標を持つ Java Beans を考えましょう。

通常の Java Beans であれば、x 座標も y 座標も double で表します。しかし、この場合、値が変化したかどうかをチェックすることが煩雑になってしまいます。

そこで、JavaFX ではプロパティクラスで元々のプロパティをラップしてしまいます。たとえば、xy 座標を表すには double クラスを使うのではなく、DoubleProperty クラスを使用します。

このプロパティに対して、プロパティ間の同期を行うバインドや、値の変更に応じたイベント処理を記述することができます。

JavaFX ではプリミティブ、文字列、任意のオブジェクトなどのプロパティクラスが提供されています。

ここでは、TableView で表示することを考えて、site と url の 2 プロパティを保持する Java Beans を作成します。

site と url はいずれも文字列なので、StringProperty クラスを使用します。url が文字列にしたのは、JavaFX の組み込みブラウザで Web ページをロードする時に URL を文字列で指定するためです。

もし、URL クラスで表すとしたら ObjectProperty クラスを使用します。

では、Java Bean である Bookmark クラスを作成していきましょう。

  1. まずは、site と url という 2 つのプロパティの定義を行う部分までを記述します。StringProperty クラスは抽象クラスなので、ここではコンクリートクラスの 1 つである SimpleStringProperty クラスを使用しました。
    package bookmarktable;
    
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    public class Bookmark {
        private StringProperty site = new SimpleStringProperty();
        private StringProperty url = new SimpleStringProperty();
    }
  2. 次にコンストラクタを作成します。プロパティへの値の設定は set メソッド、値の取得は get メソッドで行います。ここでは、値の設定を行うため、set メソッドを使用し、コンストラクタの引数をプロパティにセットします。
    public class Bookmark {
        private StringProperty site = new SimpleStringProperty();
        private StringProperty url = new SimpleStringProperty();
        
        public Bookmark(String site, String url) {
            this.site.set(site);
            this.url.set(url);
        }
    }
  3. JavaFX の Java Beans ではお約束事として、プロパティを取得するためのメソッドを定義します。メソッド名は プロパティ名 + Property という命名規則に沿って、作成します。
    public class Bookmark {
        private StringProperty site = new SimpleStringProperty();
        private StringProperty url = new SimpleStringProperty();
        
        public Bookmark(String site, String url) {
            this.site.set(site);
            this.url.set(url);
        }
    
        public StringProperty siteProperty() {
            return site;
        }
        
        public StringProperty urlProperty() {
            return url;
        }
    }
  4. 最後に一般的な Java Beans と同様に getter/setter メソッドを定義します。getter ではプロパティの get メソッド、setter では set メソッドを使用します。
    public class Bookmark {
        private StringProperty site = new SimpleStringProperty();
        private StringProperty url = new SimpleStringProperty();
        
        public Bookmark(String site, String url) {
            this.site.set(site);
            this.url.set(url);
        }
    
        public StringProperty siteProperty() {
            return site;
        }
        
        public StringProperty urlProperty() {
            return url;
        }
    
        public String getSite() {
            return site.get();
        }
    
        public void setSite(String site) {
            this.site.set(site);
        }
    
        public String getUrl() {
            return url.get();
        }
    
        public void setUrl(String url) {
            this.url.set(url);
        }
    }

 

これで、テーブルで表示する Java Beans の Bookmark クラスができました。

 

TableView とデータの対応づけ

TableView クラスで表示する Bookmark クラスが作成できたので、Bookmark クラスと TableView クラス (TableColumn クラス) との対応づけを行いましょう。

対応づけを行うには、FXML の BookmarkView.fxml で <TableView> 要素などに fx:id 属性を追加する必要があります。ここでは次のように fx:id 属性を指定しました。

<BorderPane prefHeight="600.0" prefWidth="800.0" 
            xmlns:fx="http://javafx.com/fxml/1" 
            xmlns="http://javafx.com/javafx/2.2"
            fx:controller="bookmarktable.BookmarkViewController">
  <top>
    <TableView fx:id="table" prefHeight="200.0" prefWidth="200.0">
      <columns>
        <TableColumn minWidth="50.0" prefWidth="290.0" text="Site" fx:id="siteColumn" />
        <TableColumn minWidth="100.0" prefWidth="490.0" text="URL" fx:id="urlColumn" />
      </columns>
    </TableView>
  </top>
</BorderPane>

<TableView> 要素が table、site カラムが siteColumn、url カラムがurlColumn としています。

次に、コントローラクラスである BookmarkViewControler クラスです。

TableView クラスや TableColumn クラスはジェネリクスのパラメータを指定する必要があります。TableView クラスでは、表示する Java Beans のクラスを指定します。TableColumn クラスには、表示する Java Beans のクラスと、カラムのセルで表示するクラスを指定します。

public class BookmarkViewController implements Initializable {
    @FXML
    private TableView<Bookmark> table;
    @FXML
    private TableColumn<Bookmark, String> siteColumn;
    @FXML
    private TableColumn<Bookmark, String> urlColumn;

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

前半で行った Hello プロジェクトの場合と同じように、@FXML アノテーションを付加して宣言します。

次に Java Beans を保持するリストを作成します。通常の Java であれば、リストには java.util.List インタフェースを使用しますが、JavaFX の場合プロパティと同じようにバインドや要素が変化したときのイベント処理などを加えたリストを使用します。

public class BookmarkViewController implements Initializable {
    // テーブルに表示するブックマークのリスト
ObservableList<Bookmark> bookmarks = FXCollections.observableArrayList(); @FXML private TableView<Bookmark> table; @FXML private TableColumn<Bookmark, String> siteColumn; @FXML private TableColumn<Bookmark, String> urlColumn; @Override public void initialize(URL url, ResourceBundle rb) { // テーブルに表示するブックマーク bookmarks.addAll( new Bookmark("Google", "https://www.google.co.jp/"), new Bookmark("Yahoo!", "http://www.yahoo.co.jp/"), new Bookmark("Facebook", "https://www.facebook.com/"), new Bookmark("Twitter", "https://twitter.com/") ); } }

ObjservableList クラスは抽象クラスなので、直接 new することはできません。そこで、ユーティリティクラスの FXCollections クラスのファクトリメソッド observableArrayList メソッドを使用して作成します。

実際のブックマークは initialize メソッドに記述しました。ObservableList クラスも java.util.List インタフェースを実装しているので、要素の追加や取得などは同じように行うことができます。

次に、TableColumn オブジェクトで表示する Java Beans の Bookmark クラスのプロパティを対応させます。その後、TableView オブジェクトにブックマークのリストをセットします。

これらの処理はいずれも initilalize メソッドで行います。

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // テーブルに表示するブックマーク
        bookmarks.addAll(
            new Bookmark("Google", "https://www.google.co.jp/"),
            new Bookmark("Yahoo!", "http://www.yahoo.co.jp/"),
            new Bookmark("Facebook", "https://www.facebook.com/"),
            new Bookmark("Twitter", "https://twitter.com/")
        );

        // カラムとBookmarkクラスのプロパティの対応付け
        siteColumn.setCellValueFactory(new PropertyValueFactory("site"));
        urlColumn.setCellValueFactory(new PropertyValueFactory("url"));
        
        // テーブルにブックマークをセット
        table.setItems(bookmarks);
    }
}

カラムに文字列で表すには、PropertyValueFactory クラスを使用するだけで済みます。カラムに他のノードで表示するようにするには TableCell クラスのサブクラスを実装し、Java Beans のプロパティから変換するメソッドを定義する必要があります。

ここまでで、一度実行してみましょう。TableView にちゃんとブックマークが表示できているでしょうか。

ブックマークの表示

 

組み込みブラウザ

JavaFX では HTML を表示するためのコントロールも提供されています。クラス名は WebView クラスです。

WebView クラスのベースは WebKit で、HTML 5 への対応も進められています。

ここでは、Bookmark Table の中央に WebView を表示させます。

では、また Scene Builder に戻って、BookmarkView.fxml を編集していきましょう。

  1. ライブラリペインから TableView を選択し、ルートコンテナである BorderPane の中央にドラッグ & ドロップします
    WebView のドラッグ & ドロップ
  2. WebView の fx:id は webView とします
    WebView の fx:id を設定

これで BookmarkView.fxml は完成です。最終的には以下のようになりました。

<BorderPane prefHeight="600.0" prefWidth="800.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="bookmarktable.BookmarkViewController">
  <center>
    <WebView fx:id="webView" prefHeight="200.0" prefWidth="200.0" />
  </center>
  <top>
    <TableView fx:id="table" prefHeight="200.0">
      <columns>
        <TableColumn minWidth="50.0" prefWidth="290.0" text="Site" fx:id="siteColumn" />
        <TableColumn minWidth="100.0" prefWidth="490.0" text="URL" fx:id="urlColumn" />
      </columns>
    </TableView>
  </top>
</BorderPane>

prefWidth 属性と prefHeight 属性が指定されていますが、BorderPane クラスの center プロパティは余っている領域をすべて使い切るというレイアウトを行うため、無視されます。

そして、再び BookmarkViewController クラスに戻ります。

WebView クラスは表示を行うクラスであり、HTML のパースを行うクラスは別になっています。パースを行うクラスは WebEngine クラスとなります。

そこで、BookmarkViewController クラスでも WebView クラスのフィールドだけでなく、WebEngine クラスのフィールドも定義しておきます。

  1. BookmarkViewController クラスに TableView の fx:id と同じ名前でフィールドを定義し、WebEngine クラスのフィールドも追加します
    public class BookmarkViewController implements Initializable {
        ObservableList<Bookmark> bookmarks = FXCollections.observableArrayList();
        
        @FXML
        private TableView<Bookmark> table;
        @FXML
        private TableColumn<Bookmark, String> siteColumn;
        @FXML
        private TableColumn<Bookmark, String> urlColumn;
        @FXML
        private WebView webView;
        
        private WebEngine engine;
  2. WebEngine オブジェクトは WebView オブジェクトから取得するため、initialize メソッドに追記します
        @Override
        public void initialize(URL url, ResourceBundle rb) {
            // テーブルに表示するブックマーク
            bookmarks.addAll(
                new Bookmark("Google", "https://www.google.co.jp/"),
                new Bookmark("Yahoo!", "http://www.yahoo.co.jp/"),
                new Bookmark("Facebook", "https://www.facebook.com/"),
                new Bookmark("Twitter", "https://twitter.com/")
            );
            
            // カラムとBookmarkクラスのプロパティの対応付け
            siteColumn.setCellValueFactory(new PropertyValueFactory("site"));
            urlColumn.setCellValueFactory(new PropertyValueFactory("url"));
            
            // テーブルにブックマークをセット
            table.setItems(bookmarks);
            
            // WebEngineをWebViewから取得
            engine = webView.getEngine();
  3. Web ページをロードするには WebEngine クラスの load メソッドを使用します。試しに http://www.google.co.jp/ を表示させてみましょう
            // WebEngineをWebViewから取得
            engine = webView.getEngine();
            
            engine.load("http://www.google.co.jp/");
    これで、実行すると WebView に Google のサイトが表示されます
    Google の Web サイトを表示

最後に行うのが、TableView で指定されたサイトを、WebView で表示することです。

TableView クラスで選択された行を取得するには、TableView クラスの内部クラスの TableViewSelectionModel クラスを使用します。

TableViewSelectionModel クラスに行選択の変更のイベントがあればいいのですが、残念ながらそれはありません。そこで、TableViewSelectionModel クラスのプロパティである selectedItem を使用して、行選択の変更イベントを処理することにします。

前述したように JavaFX のプロパティはバインドや、値変更のイベントを扱うことができます。値が変更した場合のイベント処理には ChangeListner インタフェースを使用します。

ここでは intialize メソッドに以下のコードを追記します。

        // テーブルの選択位置が変化したら、WebViewでそのサイトを表示
        TableView.TableViewSelectionModel<Bookmark> selectionModel = table.getSelectionModel();
        selectionModel.selectedItemProperty().addListener(new ChangeListener<Bookmark>() {
            @Override
            public void changed(ObservableValue<? extends Bookmark> value, Bookmark old, Bookmark next) {
                String url = next.getUrl();
                engine.load(url);
            }
        });

TableView で選択された行が変更すると、ChangeListener インタフェースの changed メソッドがコールされます。

changed メソッドは第 1 引数が変更した値、第 2 引数が以前の値、第 3 引数が新しい値になります。

そこで、新しい値を示す Bookmark クラスの変数 next から URL を取得し、WebEngine クラスの load メソッドの引数にします。

これで、行を選択すると、Web ページがロードされるようになります。たとえば、Yahoo! を選択すると、次のようになります。

Yahoo! を選択

 

おまけ

WebEngine クラスでは、Web ページのロードの状態を調べることができます。

ロードは非同期で行われ、SCHEDULED - RUNNING - SUCCEDDED という状態変化があります。

これを利用して、ロードが始まったら現在のページをフェードアウトさせ、新しいページのロードが完了したらフェードインするアニメーションを組み込んでみましょう。

JavaFX でアニメーションを行うには、Timeline クラスを使用する方法と、Transition クラスを使用する方法があります。Timeline クラスは記述量は多くなってしまうのですが、アニメーションの細かい制御が可能です。

逆に Transition クラスは記述が簡単なのですが、単純なアニメーションしか扱うことができません。

たとえば、ここで使用しようとしているフェードインやフェードアウトを行うアニメーションは、Transition クラスのサブクラスである FadeTransition クラスで実現できます。

他にも移動を行う TranslateTransition クラスや、回転を行う RotateTransition クラスなどが提供されています。

Transition クラスの使い方は簡単で、コンストラクタでアニメーションの時間とアニメーションさせたい対象のノードを指定します。

後はアニメーションの種類に応じたプロパティで、最初の状態と最後の状態を記述します。

アニメーションを開始するのが play メソッド、停止は stop メソッドです。

たとえば、WebView を 1 秒でフェードアウトさせるには、次のように記述します。

        FadeTransition fadeOut = new FadeTransition(Duration.millis(1_000), webView);
        fadeOut.setToValue(0.0);
        fadeOut.play();

JavaFX で時間間隔を表すには Duration クラスを使用します。時間の単位ごとにファクトリメソッドが用意されており、ここでは 1,000 ミリ秒の Duration オブジェクトを生成しています。

次の行の setToValue メソッドでアニメーション終了時の状態を指定します。0.0 ということは透明にしているということです。ここで、はじめの状態を記述していないのは、アニメーションを始める前の状態をそのまま使うためです。

最後に play メソッドでアニメーションを開始します。

アニメーションができると分かったので、ロードの状態によってアニメーションさせてみましょう。ここでも、先ほどと同様に、状態変化のイベント処理である ChangeListener インタフェースを使用します。

initialzie メソッドの最後に、次に示すコードを追加すればフェードアウト、フェードインが行われます。

        // Webページのロードの状態に応じて、アニメーションを行う
        Worker<Void> loadWorker = engine.getLoadWorker();
        loadWorker.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> ov, Worker.State old, Worker.State next) {
                if (next == Worker.State.SCHEDULED) {
                    // 次のページのローディングが始まったら、現在表示しているページをフェードアウト
                    FadeTransition fadeOut = new FadeTransition(Duration.millis(1_000), webView);
                    fadeOut.setToValue(0.0);
                    fadeOut.play();
                } else if (next == Worker.State.SUCCEEDED) {
                    // ページのローディングが完了したら、フェードイン
                    FadeTransition fadeIn = new FadeTransition(Duration.millis(1_000), webView);
                    fadeIn.setToValue(1.0);
                    fadeIn.play();
                }
            }
        });

どうなるかは、ぜひやってみてください。