列数が可変なTableView実装
本日はTableViewを使った実装。
月間の労働時間を記録する表を作ることを例にします。
縦軸にメンバーを、横軸に日付を取ることにします。
縦軸を可変にすることはできるけど、横軸を可変にするってのがよくわからない・・・・。
以下のような画面を出すための実装方法を検討してみました。
まず、データ構造ですが、日ごとの作業量を持つAmountPropertyクラス、作業者名を持つUserPropertyクラスを作成します。
UserPropertyは1ヶ月分のAmountPropertyを保持しており、ここでAmountPropertyはMapで構成し、キーは日付の情報とします。
以下の2つのPropertyクラスのソースを示します。
- UserProperty
package net.atlabo.costcalc.property; import java.util.HashMap; import java.util.Map; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; /** * ユーザ情報を保持するプロパティ * @author Atsushi Nakamoto */ public class UserProperty { /** ユーザ名称 */ private StringProperty name = new SimpleStringProperty(); /** * AmountPropetyクラスをMapにして保持するPropertyクラスのインスタンス * Mapのキー名は日付を表す文字列となる */ private ObjectProperty<Map<String, AmountProperty>> amount = new SimpleObjectProperty<Map<String, AmountProperty>>(); /** * ユーザ名称を取得する * @return */ public String getName() { return name.get(); } /** * ユーザ名称を設定する * @param name * @return */ public UserProperty setName(String name) { this.name.set(name); return this; } /** * AmountProprtyを格納したマップクラスを設定する * @param amountMap * @return */ public UserProperty setAmount(Map<String, AmountProperty> amountMap) { this.amount.set(amountMap); return this; } /** * AmountPropertyクラスを取得する * @param key マップのキー名(キー名は日付情報文字列となる) * @return */ public AmountProperty getAmountProperty(String key) { // 設定作業を行わない場合、amount.getでNULLが返却されるため、空マップを作成する if (amount.get() == null) { amount.set(new HashMap<String, AmountProperty>()); } // キー名が含まれていない場合、空のAmountPropertyクラスインスタンスを生成してマップに設定 if (!amount.get().containsKey(key)) { amount.get().put(key, new AmountProperty(0d)); } return amount.get().get(key); } }
- AmountProperty
package net.atlabo.costcalc.property; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; /** * 量を保持するプロパティ * @author Atsushi Nakamoto */ public class AmountProperty { /** 量 */ private DoubleProperty amount = new SimpleDoubleProperty(); /** * コンストラクタ * @param amount */ public AmountProperty(double amount) { this.amount.set(amount); } /** * 量をDoublePropertyのまま取得する * @return */ public DoubleProperty amountProperty() { return amount; } }
次にTableViewを表示するためのメインクラスを示します。
package net.atlabo.costcalc; import java.text.SimpleDateFormat; import java.util.*; import javafx.application.Application; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn.CellDataFeatures; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.stage.Stage; import javafx.util.Callback; import net.atlabo.costcalc.property.AmountProperty; import net.atlabo.costcalc.property.UserProperty; /** * @author Atsushi Nakamoto */ public class Main extends Application { private SimpleDateFormat df = new SimpleDateFormat("d日(EEE)"); /** * メインメソッド */ public static void main(String[] args) { Application.launch(args); } @Override public void start(Stage stage) { // カラム(列)リストを生成 List<TableColumn> columnList = new ArrayList<TableColumn>(); // TableColumnを生成してカラムリストに追加 TableColumn dateCol = new TableColumn("Member"); dateCol.setCellValueFactory(new PropertyValueFactory("name")); columnList.add(dateCol); // ダミー用データ生成 ObservableList<UserProperty> data = createDummyData(); // 今月1日を取得する final Calendar cal = new GregorianCalendar(); cal.set(Calendar.DAY_OF_MONTH, 1); // 今月末日を取得する Calendar end = new GregorianCalendar(); end.add(Calendar.MONTH, 1); end.set(Calendar.DAY_OF_MONTH, 1); end.add(Calendar.DAY_OF_MONTH, -1); // 今月1日から末日までループしてカラムを生成、カラムリストに追加する TableColumn tableColumn = null; for (; !cal.after(end); cal.add(Calendar.DAY_OF_MONTH, 1)) { // 日付を取得する(マップのキーとしても使用する) final String theDay = df.format(cal.getTime()); // テーブルカラムを生成 tableColumn = new TableColumn(theDay); // ボディ部に設定する値を設定する tableColumn.setCellValueFactory( new Callback<CellDataFeatures<UserProperty, String>, ObservableValue>() { public ObservableValue call(CellDataFeatures<UserProperty, String> p) { return p.getValue().getAmountProperty(theDay).amountProperty(); } } ); // カラムリストにカラムを追加 columnList.add(tableColumn); } // TableViewを生成 TableView table = new TableView(); // 生成したカラムリストを追加する table.getColumns().addAll(columnList); // データをテーブルの中に突っ込む table.setItems(data); Scene scene = new Scene(table); stage.setTitle("Table View Sample"); stage.setWidth(800d); stage.setHeight(640d); stage.setScene(scene); stage.show(); } /** * ダミーデータを生成する * この部分は本来DBから取得するなりファイルから取得するなり・・・・ * @return */ private ObservableList<UserProperty> createDummyData() { // 今月1日に7.75設定する Calendar cal = new GregorianCalendar(); cal.set(Calendar.DAY_OF_MONTH, 1); Map<String, AmountProperty> map = new HashMap<String, AmountProperty>(); map.put(df.format(cal.getTime()), new AmountProperty(7.75d)); return FXCollections.observableArrayList( new UserProperty().setName("Andrew"), new UserProperty().setName("Jack").setAmount(map), new UserProperty().setName("Peter"), new UserProperty().setName("George"), new UserProperty().setName("Ema"), new UserProperty().setName("Michael"), new UserProperty().setName("Hudson"), new UserProperty().setName("Lee")); } }
Mainクラスのソースを追っていきます。
startメソッドが実質メインスレッドとなっています。
まずは、ArrayListのインスタンスを生成します。
ArrayListはTableColumnの型指定をしておきます。
このオブジェクトは最後にまとめて登録するので、カラム内容は適宜このArrayListオブジェクトにaddしていくことになります。
まず最初に1番左の要素であるMember列を作成するため、TableColumnクラスのインスタンスを生成します。
コンストラクタはヘッダのラベルを示します。
更に、TableColumn#setCellValueFactoryメソッドを呼び出して、この列に表示する値を設定します。
ここのnew PropertyValueFactory("name")はUserProperty#getNameメソッドで取得できる値となりますが、
ここではまだデータとなるUserPropertyクラスのCollectionオブジェクトの設定は行っていません。
ここまでくるとArrayListインスタンスにTableColumnインスタンスを設定して1列目の設定が完了します。
ここから先が可変な列の生成になります。
今回のソースコードではシステム日付の月の1ヶ月分を可変な列とします。
ループ開始点である、今月1日とループの終端である今月末をそれぞれ保持し、
開始点から、1日ずつ日付をずらしながらforループをかけて月末までのカラムを設定します。
for文の中の説明です。
まずはTableColumnのインスタンスを生成します。
コンストラクタには日付と曜日を設定し、ヘッダのラベルを出力することにします。
次が重要ですが、実際のデータ部を設定するには、先ほどのTableColumn#setCellValueFactoryを呼び出すのですが、
引数にはCallBackの無名クラスを作り、callメソッドを実装します。
callメソッドのp.getValue()で、UserPropertyのインスタンスが取得できます。
ここではUserProperty内のMapクラスにアクセスするために日付をキーにしてAmountPropertyクラスのインスタンスを取得しています。
AmountProperty#amountPropertyで設定したDoublePropertyを返却することで、これをcallメソッドの戻り値とします。
そして、TableColumnの1件1件をArrayListインスタンスにaddしていきます。
最後にTableViewクラスのインスタンスを生成します。
ここまでで生成したArrayListインスタンスを設定し、更にTableView#setItemsでデータを突っ込むことができます。
ソースコードはサンプルなので、ここではcreateDummyData()でサンプルデータを生成するようになっています。
サンプルデータ生成のソースコードが長くなっても見難いだけなので、
Jackさんの1日分に7.75を設定しているだけです。
サンプルデータはObservableList型にする必要があります。
ここまでで上記の表示ができることになります。
ああ、めちゃくちゃ読みにくい・・・・。
装飾もない見出しもないでかなり読みにくいですが、ポイントは、Callbackクラスの無名クラスを実装するする箇所かな。