JTableのセルエディタ/レンダラとBeansBinding

JTableは比較的簡単にセルを描画するセルレンダラ、セルを編集するセルエディタを追加することができる。
その中でも特に簡単なのは、カラムの型に合わせて使いたいセルエディタ/レンダラをJTableに登録する方法だ。
例えば、List型の値を編集するセルエディタをコンボボックスとしてJTableに登録する場合は以下のように記述する。

DefaultCellEditor editor = new DefaultCellEditor(new JComboBox());
table.setDefaultEditor(List.class, editor);

このコードの実行後に、TableModel#getColumnClassメソッドでList.classが返る場合は、セルエディタには上記で指定したコンボボックスが使用される訳だ。また、登録されているコンボボックスはセル毎に一つずつ生成されるのではなく共有されるため、リソースの消費は少なく抑えることができる(Flyweightパターン)

セルレンダラの場合は文字列型、又はブール型に対応する組み込みのセルレンダラを使う以外は、セルエディタのように基底のクラスとなるDefaultCellEditorのような実装が用意されていないので、入力する形式に合わせて適当なJComponentから拡張して、TableCellRendererインタフェースを実装したクラスを用意する。例えば、List.classを編集するためにJComboBoxをセルレンダラとして利用するクラスであるComboBoxCellRendererクラスは以下のように書ける。

public class ComboBoxCellRenderer extends JComboBox implements TableCellRenderer {
    private DefaultComboBoxModel model;
    public ComboBoxCellRenderer(JComboBox base) {
        super();
        this.initialize(base.getModel());
    }
    private void initialize(ListModel listModel) {
        Object[] items = new Object[listModel.getSize()];
        for ( int i = 0; i < items.length; i++ ) {
            items[i] = listModel.getElementAt(i);
        }
        this.model = new DefaultComboBoxModel(items);
        this.setModel(this.model);
        this.setBorder(BorderFactory.createEmptyBorder());
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        if (isSelected) {
            setForeground(table.getSelectionForeground());
            super.setBackground(table.getSelectionBackground());
        } else {
            setForeground(table.getForeground());
            setBackground(table.getBackground());
        } 

        int index = this.model.getIndexOf(value);
        if ( index != -1 ) {
            this.setSelectedIndex(index);
        } else {
            this.setSelectedIndex(0);
        }
        return this;
    }
}

これでセルエディタ同様に、カスタムなセルレンダラをJTableに登録することができる。

ComboBoxCellRenderer renderer = new ComboBoxCellRenderer(new JComboBox()); 
table.setDefaultRenderer(List.class, renderer);

なお、ComboBoxCellRendererはJComboBoxを継承しているので、そのままDefaultCellEditorのコンストラクタに指定することで、セルエディタとレンダラでGUIコンポーネントを共有することもできる。

さて、BeansBindingはJTableのカラムを任意のJavaBeansのプロパティにバインドできるが、その場合に上記のようにセルエディタ/レンダラを切り替えたい場合はどうすれば良いのだろう。

この問いの答えで一番簡単なのは、カラム毎にバインディングを定義するColumnBindingクラスでカラムの型を指定することだ。例えば、Hoge型のfooプロパティがBoolean型だとすると、このプロパティをJTableのカラムにバインドする場合は以下のように記述する。

List hogeList = new ArrayList();
hogeList.add(new Hoge());
:
JTableBinding binding = SwingBindings.createJTableBinding(UpdateStrategy.READ_WRITE
            , ObservableCollections.observableList(hogeList);
            , table);
ColumnBinding columnBinding = binding.addColumnBinding(BeanProperty.create("foo"));
columnBinding.setColumnClass(Boolean.class);

ColumnBinding#setColumnClassメソッドで登録された型は、JTableBindingクラスの内部で生成されるTableModelの実装クラスである、BindingTableModel#getColumnClassメソッドで返される値となるため、登録されている型であれば自動的に適切なセルエディタ/レンダラが適用される。