非IT企業に勤める中年サラリーマンのIT日記

非IT企業でしかもITとは全く関係ない部署にいる中年エンジニア。唯一の趣味がプログラミングという”自称”プログラマー。

JavaFXのTableViewの編集後の値が取得できずハマったけど何とか解決できたのでシェアする

   

JavaFXのTableViewクラスについて直近何記事か書いてきましたが、編集後セルの値を取得できずにドハマりしました。で、何とか切り抜けられたので記事にします。

JavaのGUIコントロールの特徴として、見た目を制御するクラス(今回であればTableViewクラス)、値を保持するためのクラス(ObservableListクラス)、データの型式を決めるデータクラスとわかれていて、ここがJavaを難しくしてしまう原因だと(僕は)思います。

これがC#だったらテーブルクラスにすべて同梱されていて使う側にとっては非常にシンプルなのです。Javaは切り離れている分学習コストが高い!(と僕は思います)

愚痴はこの辺にしておいて、ハマったところとその後切り抜けた解決策を下記します。

広告

ハマったところ

ソースコードは後で出しますがベースは以下の記事とほぼ同じです。編集可能にしたくらいです。

 

ボタンの位置がちょっと違うのですがまあだいたい同じです。

で、ボタンを押したら、名前列(1列目)の1行目の値をコンソールに表示するようにしています。下図の通りボタンを押したら「鈴木」という文字がコンソールに出てきます。

 

さて、ここで「鈴木」という文字を「鈴木太郎」に編集してみます。

 

編集後、もう一度ボタンを押したらどうなるか?…なんと編集前の「鈴木」が出てきました。編集が反映されないのです。

ソースコード(誤り)

まずは本体のソースコードです。先に言っておくとこれには誤りはありません。後述するデータクラスに誤りがありました。

import javafx.application.Application;
import javafx.fxml.*;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.layout.*;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.collections.*;
import javafx.scene.control.cell.*;

public class TableTest extends Application implements Initializable {
  @FXML private TableView table;
  @FXML private TableColumn nameCol;
  @FXML private TableColumn ageCol;
  @FXML private TableColumn homeCol;
  private ObservableList<Member> data;

  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage stage) throws Exception{
    VBox root = FXMLLoader.load(getClass().getResource("TableTest.fxml"));
    stage.setTitle("TableView Test");
    stage.setScene(new Scene(root));
    stage.show();
  }

  @Override
  public void initialize(java.net.URL url, java.util.ResourceBundle bundle) {
    data = FXCollections.observableArrayList();
    table.itemsProperty().setValue(data);
    table.setItems(data);

    nameCol.setCellValueFactory(new PropertyValueFactory<Member, String>("name"));
    ageCol.setCellValueFactory(new PropertyValueFactory<Member, String>("age"));
    homeCol.setCellValueFactory(new PropertyValueFactory<Member, String>("home"));
    //編集可能にするためにセルにTextFieldを仕込む
    nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
    ageCol.setCellFactory(TextFieldTableCell.forTableColumn());
    homeCol.setCellFactory(TextFieldTableCell.forTableColumn());

    data.addAll( new Member("鈴木", "48", "神奈川県") );
    data.addAll( new Member("山田", "21", "栃木県") );
    data.addAll( new Member("佐藤", "36", "東京都") );
  }

  @FXML
  public void onClick(ActionEvent e) {
    System.out.println("Name: " + data.get(0).getName());
  }
}
 

 

前回と異なる点としてセルを編集するのに列ごとにTextFieldを仕込む必要があります。

nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
 

 

TableViewのデータを扱うためにObservableListオブジェクトをTableViewにリンクさせます。

//private ObservableList<Member> data;
data = FXCollections.observableArrayList();
table.itemsProperty().setValue(data);
table.setItems(data);

 

で、ボタンクリックでObservableListオブジェクトであるdataから、1行目get(0)から名前getName()をコンソールに表示させているわけです。

@FXML
public void onClick(ActionEvent e) {
  System.out.println("Name: " + data.get(0).getName());
}
 

 

ここまでは問題ありません。以上のソースコードは対策後も一切修正していません。

しかし、先ほど説明した通り最初からソースコード上で仕込んだデータは取得できたものの、後からテーブル上で修正したデータが取得できない事態になってしまったのです。TableViewオブジェクトとObservableListオブジェクトがちゃんと同期が取れてできないんですね。

 

次に問題のデータクラスです。これは前回記事と全く同じものでTableViewとうまく同期が取れていたものと思っていましたが、今回の問題の原因は実はここです。

public class Member {
  private String name;
  private String age;
  private String home;

  public Member(String name, String age, String home) {
    this.name = name;
    this.age = age;
    this.home = home;
  }

  public String getName(){ return name; }
  public String getAge(){ return age; }
  public String getHome(){ return home; }
  public void setName(String name){ this.name = name; }
  public void setAge(String age){ this.age = age; }
  public void setHome(String home){ this.home = home; }
}
 

 

一見何の問題もな言うように見える上記のソースコードですが、いったい何が問題だったのか? 次に示したいと思います。

 

データクラスをどうすればよいか?

データクラスを以下のソースコードにすれば正解です。インスタンス変数をString型ではなくSimpleStringPropertyにすればOKです。int型ならSimpleIntegerProperty、boolean型ならSimpleBooleanProperty と、プリミティブ型ごとに用意されています。

なぜプリミティブ型ではダメなのか不勉強でわかっていないのですが(好きなだけクラス型を指定できてしまうから? 許可された型しか使えないようにするため?)、ここに行き着くまで結構時間食いましたね…。こういうところがいかにもJavaらしいというか。

import javafx.beans.property.*;

public class Member {
  private final SimpleStringProperty name;
  private final SimpleStringProperty age;
  private final SimpleStringProperty home;

  public Member(String name, String age, String home) {
    this.name = new SimpleStringProperty(name);
    this.age = new SimpleStringProperty(age);
    this.home = new SimpleStringProperty(home);
  }

  public StringProperty nameProperty(){return name;}
  public StringProperty ageProperty(){return age;}
  public StringProperty homeProperty(){return home;}
  public String getName() {return name.get();}
  public String getAge() {return age.get();}
  public String getHome() {return home.get();}
  public void setName(String name) {this.name.set(name);}
  public void setAge(String age) {this.age.set(age);}
  public void setHome(String home) {this.home.set(home);}
}
 

 

このデータクラスを使えば以下の通り編集後のセルのデータを取得することができました。

 

広告

 - Java