JavaFX 2のTableViewを使う際、行データをインサート、削除する場合は自動的に描画が更新されます。しかし、既にTableViewが抱える行データの列(属性)の値を変更するときは、注意点が必要です。
次のJavaFXのチュートリアルでTableViewのサンプルが紹介されています。
TableViewの行データを表すPersonクラス(ネストクラス)のコード
public static class Person { private final SimpleStringProperty firstName; private final SimpleStringProperty lastName; private final SimpleStringProperty email; private Person(String fName, String lName, String email) { this.firstName = new SimpleStringProperty(fName); this.lastName = new SimpleStringProperty(lName); this.email = new SimpleStringProperty(email); } public String getFirstName() { return firstName.get(); } public void setFirstName(String fName) { firstName.set(fName); } public String getLastName() { return lastName.get(); } public void setLastName(String lName) { lastName.set(lName); } public String getEmail() { return email.get(); } public void setEmail(String email) { email.set(email); } }
このPersonをデータとするTableViewがあります。
たとえば、emailを変更した人物がいた場合、理想では
Person person = ...
person.setEmail(newEmail);
とデータを変更するだけで表示が更新されるのが理想です。
しかし、実際はこれではTableViewの変更がかかりません。
スクロールをする等で該当人物の表示行が更新されると、
email欄も変更後になります。
つまり、TableViewが持つObservableListにPersonインスタンスを
出し入れするような変更を行ったときは、その変更を検知してTableViewの
表示が変わります。しかし、ObservableListの1個の要素であるPerson
インスタンスの属性だけが変わるような変更を行ったときは、その変更を
TableViewが検知することができないようです。
この問題の回避策として、TableViewが持つObservableListの1つ目の
要素を削除して再挿入する、TableViewのlayoutメソッドを呼ぶ、などの
手段でTableViewに再描画させる方法を見かけました。
しかし、属性1つの変更でTableView全体を再描画するのは効率が悪く
なるべく避けたいところです。
解決策は、行を表すデータクラスにバインドのためのオブジェクトである
SimpleStringPropertyを取り出すメソッドを決まった命名規約で定義する
ことです。
Personクラスに追加するメソッド
public StringProperty firstNameProperty() { return firstName; } public StringProperty lastNameProperty() { return lastName; } public StringProperty emailProperty() { return email; }
ここで、データを表の列に対応づけているコードを抜粋します。
firstNameCol.setCellValueFactory( new PropertyValueFactory<Person, String>("firstName") ); ... 略
ここで、PropertyValueFactoryクラスのJavadocを見ると
この例では、文字列"firstName"が指定されたことにより、Personクラス型にfirstNameProperty() メソッドがあることが想定される。このメソッドはPropertyインスタンスを返却しなければならない。...(中略)...さらに、TableViewは自動的にこの戻り値に対するオブザーバーを設ける。オブザーバーに変更が伝達されると、TableViewは直ちにセルを更新する。
とあります。
また、次の記事(日本語)でもこのメソッドを定義する話が言及されています。
はまりポイント
スペルミスには注意しましょう。
namePropertyとするところを誤ってnameProperyとしてしまい、更新されないとしばらく悩んでしまいました。
謝辞
この問題に出くわして原因がわからずTwitterにツイートしたときに、対処方法を教えていただいた@aoetkさん、@skrbさんに感謝いたします。