JTable でデータとビューをつなぐテーブルモデル
「JTable の簡単な使い方」では、JTable を用いた基本的なデータの表示方法を示しました。 そこで出来上がった画面は次のようなものでした。
簡単にデータを表示できたことは結構なことではありますが、実際に使うことを考えると問題があります。
例えば、どのセルも編集可能です。セルを選んでダブルクリック(もしくは F2 キー) でセルの編集モードになります。
さらにデータとして true または false しか保持してほしくないような場所にも、任意の文字が入力できてしまいます。
どうしてこんな事が起きるのかというと、 JTable はデフォルトで全てのデータを String として処理するからです。 このため、真偽値 (true/false) としてデータを渡したつもりでも、String の "true" または "false" を渡したことと同じことになり、 それが画面に表示されていたというわけです。
「JTable の簡単な使い方」でみた、 JTable のコンストラクタにデータとカラム名を渡してデータを表示するというのは、 簡単で結構ですが、それだけのものです。
JTable (= ビュー) とデータの仲介役は「テーブルモデル」
実際のデータは、データベースに格納されていたり、ファイルに書き込まれていたり、あるいはメモリー上にのっているだけの場合もあるでしょう。
こうした様々な状況に対応できるように、通常 JTable にデータを直接保持させることはしません。
その代わりに「テーブルモデル」というオブジェクトを用意し、テーブルモデルを用いてビュー(=JTable オブジェクト) とデータと継ぐようにします。
JTable がカラムヘッダー名を知りたいときはテーブルモデルに問い合わせます。
JTable があるセルの値を知りたい場合もテーブルモデルに問い合わせます。
JTable がデータの行数を知りたい場合もテーブルモデルに問い合わせます。
JTable がどのカラムがどんな型のデータを持っているか知りたいときもテーブルモデルに問い合わせます。
・・・
このようにして、ビューはデータを保持することもなければ、どこにデータを書き込むかとか、そういった詳細も知らなくて済むようになります。 JTable はテーブルモデルオブジェクトとそれへの問い合わせ方法を知っていれば十分です。
テーブルモデルの実装方法
そんな便利な「テーブルモデル」ですが、どうやって実装すればよいのでしょうか?
テーブルモデルは、TableModel インターフェイスを実装したオブジェクトです。 TableModel インターフェイスはそんなに複雑なインターフェイスでもないので、実装しちゃえば良いような気もしなくもありません。
しかし、実際のところテーブルモデルに受け持ってもらいたい役割は、イベントの生成などの少し厄介なことも含まれます。 このため、全て自力で実装するのは少々面倒な作業になります。
そこで AbstractTableModel クラスを利用します。 AbstractTableModel クラスでは、テーブルモデルで必要とされるメソッドが実装されているので、 これを派生することで簡単に独自のテーブルモデルを実装することができます。
テーブルモデルの実装例
それでは、「JTable の簡単な使い方」で使ったコードを例にとり、 AbstractTableModel のサブクラスを作成して、独自のテーブルモデルクラスを実装してみましょう。
ここでは MyTableModel1 という名前のテーブルモデルクラスにします。
package com.keicode.java.testapp;
import javax.swing.table.AbstractTableModel;
@SuppressWarnings("serial")
class MyTableModel1 extends AbstractTableModel {
Object[][] data = {
{"X", "100", true},
{"X", "200", false},
{"X", "300", false},
{"Y", "400", false},
{"Y", "500", false},
{"Z", "600", false},
{"Z", "700", false}};
String[] columns = {"Column 0", "Column 1", "Column 2"};
@Override
public String getColumnName(int column) {
return columns[column];
}
@Override
public int getRowCount() {
return data.length;
}
@Override
public int getColumnCount() {
return columns.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return data[rowIndex][columnIndex];
}
}
ここでは最低限のメソッドだけをオーバーライドしています。
getColumnName メソッドは、カラム(列)の 0 ゼロベースインデックスを受け取り、カラム名を String で返します。
getRowCount メソッドはデータの行数を返します。
getColumnCount メソッドはデータのカラム数を返します。
getValueAt メソッドは、行と列のインデックスを受け取り、そのセルに設定すべき値を返します。ここでは単に配列 data からデータを取ってきて返しています。
今回のテーブルモデルはこれだけの実装です。
そして、これを使う JTable 側は次のようになります。data とか columns などの変数が消えてシンプルになりました。
package com.keicode.java.testapp;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
@SuppressWarnings("serial")
public class TableTest13 extends JFrame {
public TableTest13() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(350, 150);
JTable table = new JTable();
MyTableModel1 tableModel = new MyTableModel1();
table.setModel(tableModel);
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TableTest13 app = new TableTest13();
app.setVisible(true);
}
});
}
}
JTable のコンストラクタには何も渡さずにインスタンスを作成。引き続き、 上で作成した MyTableModel1 クラスのインスタンスを作成し、テーブルクラスの setModel メソッドでテーブルモデルを渡しています。
これを実行すると次のようなウィンドウになります。
見栄えは前回と同様です。しかし、前回と違うのは今度は全てのセルが編集不可になっていることです。
これはなぜかというと、あるセルを編集可とするか編集不可とすべきかもテーブルモデルクラスで決めることです。しかし、 今回は明示的に編集可としていないので、編集不可となります。
前回 JTable のコンストラクタにデータとカラム名を渡したときは、実は内部的に自動的にデフォルトのテーブルモデルオブジェクトが作成されます。 (その名もズバリ DefaultTableModel というクラスのオブジェクトです。)その DefaultTableModel では全てのセルを編集可としているので、JTable は全てのセルを編集可としているということです。
編集可とする方法については、次の記事で説明します。