[ Top page ] [ JavaFX index ]
JavaFX 2のUIコントロールの1つで、表(テーブル)を実現するTableViewの使い方についてまとめたものです。
JavaFXに限らず、GUIツールキットにとって表(テーブル)は主要機能ですが、機能が豊富なため使い方が複雑となっています。そこで、導入として、TableViewを使う画面レイアウトをSceneBuilderを使って作成し、サンプルコードを作成します。本文では、表(テーブル)のGUIとして実現したい事をTableViewを使ってどう実装するのかを逆引き的に記載します。
まず、JavaFX 2で簡単な表(テーブル)を使ったミニプログラムを作成します。ここでは、JavaFX 2のGUIレイアウトツールであるシーンビルダーを使ってレイアウトを作成、NetBeans側でコードを実装する方法を用います。
開発に使用したツールとバージョン 開発環境 バージョン 備考 Java SE Development Kit 7 Update10 NetBeans IDE 7.3開発版201212310001 Scene Builder 1.1 b16
JavaFXのドキュメント「Using JavaFX UI Controls(英語)」の13章 Table View にあるチュートリアルを参考に、少し応用を加えたものを作成します。
NetBeansの[ファイル]メニュー>[新規プロジェクト]を選択し、カテゴリ[JavaFX]から[JavaFX FXMLアプリケーション]を選択します。
プロジェクト名:TableSample
FXML名:TableSample
とし、あとはデフォルトのままで作成します。生成されたプロジェクトツリーを次に示します。
プロジェクトツリー上でTableSample.fxml をダブルクリックすると、シーンビルダーが起動し、雛形の画面が表示されます。
左側ペインの階層を見ると、AnchorPaneの上にButtonが1つとLabelが1つ貼られています。NetBeansが生成する雛形の画面レイアウト(FXML)です。
まず、ボタンを右下にでも寄せましょう。シーンビルダーの真ん中の画面レイアウト上のボタン("Click Me!"と書かれているもの)をドラッグ&ドロップで画面右下隅に移動させます。
次に左側ペインのライブラリからTable Viewを真ん中の画面レイアウト上にドラッグ&ドロップします。画面左上にTable Viewの左上を合わせる感じで、まずは下がはみ出るのは気にしません。
位置をだいたいの感覚でそろえます。
この時点でのコントロールの階層(シーンビルダー左側ペイン下半分)は次のようになっています。
TableViewは、デフォルトで2つの列(TableColumn)を持っているのが画面レイアウトからも階層からも分かります。
Labelはとりあえず使わないので、階層のツリー上でLabelを選択して右クリックし、ポップアップメニューの[削除]を実行しておきます。
TableViewの列を増やすには、左側ペイン上のライブラリから、Table Columnを選択し、真ん中画面レイアウトのTable View上でドラッグ&ドロップします。直後の画面レイアウトは次になります。
まず、列名を変更していきます。変更したい列を選択し、右側ペインのプロパティで[Text]欄に記載されているデフォルトの「列X」を書き換えます。書き換え作業イメージを次の画面に示します。
(画像をクリックすると実寸大画像を表示します)
列見出しの境界付近にカーサーを持っていくと、カーサーが両矢印形態になり、列サイズ変更が可能になります。
適当に列幅を変えたら、シーンビルダーの[プレビュー]メニュー>[ウィンドウでプレビュー]を実行すると、実際に独立したウィンドウとして画面が現れます。次に画面キャプチャを示します。
このウィンドウの大きさを大きくする方向へリサイズしてみます。すると、各コントロールは左上からの現在位置と大きさを保ったまま空白だけが広がっていくのが分かります。ウィンドウを広げたときの画面キャプチャを次に示します。
まず、ボタンはウィンドウのリサイズに対して常にウィンドウ左下隅に位置するよう設定します。シーンビルダー上でボタンを選択し、右側ペインのレイアウトを開いた状態にします。AnchorPane制約の図の部分で、右側、下側の点線をクリックし赤い線になった状態にします。
再度[プレビュー]メニュー>[ウィンドウでプレビュー]を実行し、表れたウィンドウの大きさを変ると、ボタンがウィンドウの右下隅を維持するのが分かります。
次にTableViewは、ウィンドウの大きさに合わせて大きくなったり小さくなったりするよう設定します。シーンビルダ上でTable Viewを選択し、右側ペインのレイアウトのAnchorPane制約の上下左右を全て赤にします。
再度[プレビュー]メニュー>[ウィンドウでプレビュー]を実行し、表れたウィンドウの大きさを変ると、Table Viewが画面の大きさに合わせて幅/縦双方向に伸び縮みするのが分かります。
ここで、いったんシーンビルダーからJavaのコードに移ります。シーンビルダーで作成した画面レイアウトには、対応するJavaのコントローラクラスが定義されます。対応するコントローラクラスの確認・変更は、左側ペイン階層ツリーで最上位のコントロール(このサンプルの場合AnchorPane)を選択状態にし、右側ペインの[コード]を展開し、[コントローラ・クラス]欄で行います。次の画面に示します。
(画像をクリックすると実寸大画像を表示します)
再びNetBeansに戻って、プロジェクトツリー上でTableSampleController.java をダブルクリックすると、ソースファイル編集領域に雛形のソースコードが表示されます。ソースコードを次に示します。
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package tablesample; import java.net.URL; import java.util.ResourceBundle; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Label; /** * * @author Toru Takahashi */ public class TableSampleController implements Initializable { @FXML private Label label; @FXML private void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); label.setText("Hello World!"); } @Override public void initialize(URL url, ResourceBundle rb) { // TODO } }
コントローラの雛形には、Javaコード側で操作したいUIコントロールのインスタンス(例:label)、UIコントロールでイベントが発生したときに呼び出してもらうメソッド(例:handleButtonAction)、そして初期化コードを定義するメソッド(例:initialize)が記載されています。
まず、画面レイアウトの雛形から削除したLabelに対応するフィールドlabelとそれを使うコードを削除します。次にTableViewおよびTableColumn(3つ)のインスタンスに対応するフィールドを定義します。TableViewの場合は、Java側のコードでそれぞれに対して操作を行うためです。これらの修正をしたソースコードを次に示します。
package tablesample; import java.net.URL; import java.util.ResourceBundle; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; public class TableSampleController implements Initializable { @FXML private TableView<Person> table; @FXML private TableColumn idColumn; @FXML private TableColumn nameColumn; @FXML private TableColumn birthYearColumn; @FXML private void handleButtonAction(ActionEvent event) { System.out.println("You clicked me!"); } @Override public void initialize(URL url, ResourceBundle rb) { // TODO } }
JavaFX APIのJavadocでTableViewを見ると、「Class TableView<S>」とジェネリクスになっています。型パラメータに指定する型は、テーブルの1行分のデータを保持するインスタンスの型となります。このサンプルでは、テーブルに表示するID、名前、誕生年の3つを必ず含む型として後ほどPersonクラスを定義することとします。
シーンビルダーで、UIコントロールとコントローラクラスの@FXMLアノテーションフィールドを対応づけます。すると、実行時にFXMLファイルを読み込んだ際、このフィールドに実際のインスタンスが「注入」されます。TableColumnについてシーンビルダーで対応付ける画面を次に示します。
(画像をクリックすると実寸大画像を表示します)
この画面で、UIコントロールのプロパティのfx:id欄のコンボボックスをドロップダウンすると、コントローラクラスの@FXMLアノテーションフィールドの変数名一覧が出ます。スペルミスを防ぐために、先にコントローラクラスで@FXMLアノテーションフィールドを定義し、それからシーンビルダーのプロパティ fx:idを選択するのがよいと思います。なお、NetBeansでフィールドを記述した後、保存操作をしてコンパイルさせた状態でシーンビルダーを操作しないとfx:id欄の候補に出てこないようです。
3つのTableColumnおよびTableViewについてfx:idを指定します。
さて、テーブルに表示する1行分のデータを表すPersonクラスを記述します。NetBeansの[ファイル]メニュー>[新規ファイル]で、カテゴリ[Java]から[Javaクラス]を選択し、クラス名Person、パッケージtablesampleとして作成します。テーブルに表示する3つの属性を定義したPersonクラスのソースコードを次に示します。
package tablesample; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; public class Person { private IntegerProperty id; private StringProperty name; private IntegerProperty birthYear; public Person(int anId, String aName, int aBirthYear) { id = new SimpleIntegerProperty(anId); name = new SimpleStringProperty(aName); birthYear = new SimpleIntegerProperty(aBirthYear); } public IntegerProperty idProperty() { return id; } public StringProperty nameProperty() { return name; } public IntegerProperty birthYearProperty() { return birthYear; } }
JavaFXのBeansは、思いっきり「プロパティ」というクラスを使って値の更新を知るようになっています。プロパティクラスは、整数、文字列とデータ種類ごとにクラスが用意されています。整数値であるIDの場合、フィールドに保持するプロパティはIntegerProperty型(抽象クラス)とし、実装型はここではSimpleIntegerProperty型を使っています。このフィールドのgetterメソッドは命名規約が定まっており、プロパティ名+Property です。
ふたたびコントローラ(TableSampleController.java)に戻って、画面レイアウトしたTableView、TableColumn(3つ)と、テーブルのデータであるPersonクラスの間を関係付けるコードを追加します。初期化メソッドinitializeの中に記述します。また、実行時にデータが1つ見えるようサンプルデータを1件登録するコードも書いています。
@Override public void initialize(URL url, ResourceBundle rb) { idColumn.setCellValueFactory(new PropertyValueFactory<Person, Integer>("id")); nameColumn.setCellValueFactory(new PropertyValueFactory<Person, String>("name")); birthYearColumn.setCellValueFactory(new PropertyValueFactory<Person, Integer>("birthYear")); // サンプルデータを1行追加 table.getItems().add(new Person(101, "Lucius Junius Brutus", -550)); }
ここでプログラムを実行すると、データが1行表示された画面が表れます。
ボタンを押すと、1行目の要素の誕生年を1つ増やすという処理を追加します。TableSampleController.javaのボタンをクリックしたときに呼ばれるメソッドに記述します。
@FXML private void handleButtonAction(ActionEvent event) { table.getItems().get(0).birthYearProperty().set(-549); }
TableViewクラスのgetItems()メソッドで、テーブルに表示しているデータのリストを取得します。get(0)で1つ目のデータのインスタンスを取得します。誕生年を保持するbirthYearPropertyを取得して、setメソッドで更新する値を入れます。
birthYearPropertyはIntegerProperty型で、これは値の変更をリスナーに通知する機能を持っており、birthYearColumnに結び付けられています。
ただ、普通のBeansと違ってPropertyを取得してから値を読み書きするのは見た目に美しくはありません。そこで、普通のBeansのようにget/setメソッドをPersonクラスに追加します。
public int getBirthYear() { return birthYear.get(); } public void setBirthYear(int year) { birthYear.set(year); }
すると、ボタンをクリックしたときの呼び出しメソッドの記述を少し簡単にできます。
@FXML private void handleButtonAction(ActionEvent event) { table.getItems().get(0).setBirthYear(-549); }
T.B.D.