31 March 2009

BeanTableModel as a TableModel for Swing

I've written lots of models for JTable's in Swing. Often I have to re-learn how Swing TableModel work each time. There is also often the case that one row in the table represents a Java object with bean getters and setters. Inspired by the Spring bean configuration I wrote TableModel that uses a bean class as a base.

It makes it easy to create a table and sets up the default CellEditors automatically and can get and set data from the cell in the GUI to the Java object.


BeanTableModel:
package se.lesc.beantablemodel;

import java.beans.*;
import java.util.*;

import javax.swing.table.AbstractTableModel;

/**
* A table model where each row represents one instance of a Java bean.
* When the user edits a cell the model is updated.
*
* @author Lennart Schedin
*
* @param <M> The type of model
*/
@SuppressWarnings("serial")
public class BeanTableModel<M> extends AbstractTableModel {
private List<M> rows = new ArrayList<M>();
private List<BeanColumn> columns = new ArrayList<BeanColumn>();
private Class<?> beanClass;

public BeanTableModel(Class<?> beanClass) {
this.beanClass = beanClass;
}

public void addColumn(String columnGUIName, String beanAttribute,
EditMode editable) {
try {
PropertyDescriptor descriptor = new PropertyDescriptor(beanAttribute,
beanClass);
columns.add(new BeanColumn(columnGUIName, editable, descriptor));
} catch (Exception e) {
e.printStackTrace();
}
}

public void addColumn(String columnGUIName, String beanAttribute) {
addColumn(columnGUIName, beanAttribute, EditMode.NON_EDITABLE);
}

public void addRow(M row) {
rows.add(row);
}

public void addRows(List<M> rows) {
for (M row : rows) {
addRow(row);
}
}

public int getColumnCount() {
return columns.size();
}

public int getRowCount() {
return rows.size();
}

public Object getValueAt(int rowIndex, int columnIndex) {
BeanColumn column = columns.get(columnIndex);
M row = rows.get(rowIndex);

Object result = null;
try {
result = column.descriptor.getReadMethod().invoke(row);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

public void setValueAt(Object value, int rowIndex, int columnIndex) {
M row = rows.get(rowIndex);
BeanColumn column = columns.get(columnIndex);

try {
column.descriptor.getWriteMethod().invoke(row, value);
} catch (Exception e) {
e.printStackTrace();
}
}

public Class<?> getColumnClass(int columnIndex) {
BeanColumn column = columns.get(columnIndex);
Class<?> returnType = column.descriptor.getReadMethod().getReturnType();
return returnType;
}

public String getColumnName(int column) {
return columns.get(column).columnGUIName;
}


public boolean isCellEditable(int rowIndex, int columnIndex) {
return columns.get(columnIndex).editable == EditMode.EDITABLE;
}

public List<M> getRows() {
return rows;
}

public enum EditMode {
NON_EDITABLE,
EDITABLE;
}

/** One column in the table */
private static class BeanColumn {
private String columnGUIName;
private EditMode editable;
private PropertyDescriptor descriptor;

public BeanColumn(String columnGUIName, EditMode editable,
PropertyDescriptor descriptor) {
this.columnGUIName = columnGUIName;
this.editable = editable;
this.descriptor = descriptor;
}
}
}

A test class:
package se.lesc.beantablemodel;

import javax.swing.*;

import se.lesc.beantablemodel.BeanTableModel.EditMode;

@SuppressWarnings("serial")
public class TableTester extends JTable {

public TableTester() {
BeanTableModel<Person> model = new BeanTableModel<Person>(Person.class);
model.addColumn("Social Security Number", "socialSecurityNumber");
model.addColumn("Name", "name", EditMode.EDITABLE);
model.addColumn("Age", "age", EditMode.EDITABLE);
model.addColumn("Heigt (in cm)", "height", EditMode.EDITABLE);
model.addColumn("Has empoyment", "employed", EditMode.EDITABLE);

model.addRow(new Person("1", "David", 20, 170.5, true));
model.addRow(new Person("2", "Susan", 26, 162.0, false));
model.addRow(new Person("3", "Mark", 42, 180.5, false));
model.addRow(new Person("4", "Anna", 54, 168.0, true));
model.addRow(new Person("5", "Johan", 5, 120.5, false));

setModel(model);
}

public static class Person {
private String socialSecurityNumber;
private String name;
private Integer age;
private Double height;
private Boolean employed;
private String hiddenField;

public Person(String socialSecurityNumber, String name, Integer age,
Double height, Boolean isMale) {
this.socialSecurityNumber = socialSecurityNumber;
this.name = name;
this.age = age;
this.height = height;
this.employed = isMale;
}

public String getSocialSecurityNumber() {
return socialSecurityNumber;
}
public void setSocialSecurityNumber(String socialSecurityNumber) {
this.socialSecurityNumber = socialSecurityNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Double getHeight() {
return height;
}
public void setHeight(Double height) {
this.height = height;
}
public Boolean isEmployed() {
return employed;
}
public void setEmployed(Boolean employed) {
this.employed = employed;
}

public String getHiddenField() {
return hiddenField;
}
public void setHiddenField(String hiddenField) {
this.hiddenField = hiddenField;
}
}

public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

TableTester table = new TableTester();
JScrollPane scollPane = new JScrollPane(table);
frame.setContentPane(scollPane);

frame.pack();
frame.setVisible(true);

}
}

3 comments: