与其他 Swing 模型一样(即:默认组合框模型 http://docs.oracle.com/javase/8/docs/api/javax/swing/DefaultComboBoxModel.html, 默认列表模型 http://docs.oracle.com/javase/8/docs/api/javax/swing/DefaultListModel.html) 我们可以用Generics http://docs.oracle.com/javase/tutorial/java/generics/为了创建灵活且可重用的表模型,还提供了一个 API 来与用户定义的 POJO 一起使用。
该桌子模型将具有以下特殊功能:
- 它延伸自
AbstractTableModel
利用表模型事件处理。
- Unlike
CustomerTableModel
如上所示,该表模型必须是抽象的,因为它不能覆盖getValueAt()
方法:仅仅因为我们不知道该表模型将处理的类或数据类型,所以重写上述方法的任务就留给了子类。
- 它继承空
setValueAt()
实施自AbstractTableModel
。这是有道理的,因为isCellEditable()
也是从该类继承并始终返回false
.
- 默认实现
getColumnClass()
也是继承的并且总是返回Object.class
.
这些功能使这个表模型非常容易根据我们的要求实现:
- 如果我们需要显示一个只读表,那么我们必须重写 top 的 2 个方法:
getValueAt()
and getColumnClass()
(最后一项是推荐的,但不是强制性的)。
- 如果我们的表格需要可编辑,那么我们必须重写顶部的 4 个方法:上面提到的两个加上
isCellEditable()
and setValueAt()
.
让我们看一下表模型的代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.AbstractTableModel;
/**
* Abstract base class which extends from {@code AbstractTableModel} and
* provides an API to work with user-defined POJO's as table rows. Subclasses
* extending from {@code DataObjectTableModel} must implement
* {@code getValueAt(row, column)} method.
* <p />
* By default cells are not editable. If those have to be editable then
* subclasses must override both {@code isCellEditable(row, column)} and
* {@code setValueAt(row, column)} methods.
* <p />
* Finally, it is not mandatory but highly recommended to override
* {@code getColumnClass(column)} method, in order to return the appropriate
* column class: default implementation always returns {@code Object.class}.
*
* @param <T> The class handled by this TableModel.
* @author dic19
*/
public abstract class DataObjectTableModel<T> extends AbstractTableModel {
private final List<String> columnNames;
private final List<T> data;
public DataObjectTableModel() {
this.data = new ArrayList<>();
this.columnNames = new ArrayList<>();
}
public DataObjectTableModel(List<String> columnIdentifiers) {
this();
if (columnIdentifiers != null) {
this.columnNames.addAll(columnIdentifiers);
}
}
@Override
public int getRowCount() {
return this.data.size();
}
@Override
public int getColumnCount() {
return this.columnNames.size();
}
@Override
public String getColumnName(int columnIndex) {
return this.columnNames.get(columnIndex);
}
public void setColumnNames(List<String> columnNames) {
if (columnNames != null) {
this.columnNames.clear();
this.columnNames.addAll(columnNames);
fireTableStructureChanged();
}
}
public List<String> getColumnNames() {
return Collections.unmodifiableList(this.columnNames);
}
public void addDataObject(T dataObject) {
int rowIndex = this.data.size();
this.data.add(dataObject);
fireTableRowsInserted(rowIndex, rowIndex);
}
public void addDataObjects(List<T> dataObjects) {
if (!dataObjects.isEmpty()) {
int firstRow = data.size();
this.data.addAll(dataObjects);
int lastRow = data.size() - 1;
fireTableRowsInserted(firstRow, lastRow);
}
}
public void insertDataObject(T dataObject, int rowIndex) {
this.data.add(rowIndex, dataObject);
fireTableRowsInserted(rowIndex, rowIndex);
}
public void deleteDataObject(int rowIndex) {
if (this.data.remove(this.data.get(rowIndex))) {
fireTableRowsDeleted(rowIndex, rowIndex);
}
}
public void notifyDataObjectUpdated(T domainObject) {
T[] elements = (T[])data.toArray();
for (int i = 0; i < elements.length; i++) {
if(elements[i] == domainObject) {
fireTableRowsUpdated(i, i);
}
}
}
public T getDataObject(int rowIndex) {
return this.data.get(rowIndex);
}
public List<T> getDataObjects(int firstRow, int lastRow) {
List<T> subList = this.data.subList(firstRow, lastRow);
return Collections.unmodifiableList(subList);
}
public List<T> getDataObjects() {
return Collections.unmodifiableList(this.data);
}
public void clearTableModelData() {
if (!this.data.isEmpty()) {
int lastRow = data.size() - 1;
this.data.clear();
fireTableRowsDeleted(0, lastRow);
}
}
}
所以,采用这个表模型Customer
类,完整的实现如下所示:
String[] header = new String[] {"Entry date", "Name", "Address", "Phone number"};
DataObjectTableModel<Customer> model = new DataObjectTableModel<>(Arrays.asList(header)) {
@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0: return Date.class;
case 1: return String.class;
case 2: return String.class;
case 3: return String.class;
default: throw new ArrayIndexOutOfBoundsException(columnIndex);
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Customer customer = getDataObject(rowIndex);
switch (columnIndex) {
case 0: return customer.getEntryDate();
case 1: return customer.getName();
case 2: return customer.getAddress();
case 3: return customer.getPhoneNumber();
default: throw new ArrayIndexOutOfBoundsException(columnIndex);
}
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
if (columnIndex < 0 || columnIndex >= getColumnCount()) {
throw new ArrayIndexOutOfBoundsException(columnIndex);
} else {
Customer customer = getDataObject(rowIndex);
switch (columnIndex) {
case 0: customer.setEntryDate((Date)aValue); break;
case 1: customer.setName((String)aValue); break;
case 2: customer.setAddress((String)aValue); break;
case 3: customer.setPhoneNumber((String)aValue); break;
}
fireTableCellUpdated(rowIndex, columnIndex);
}
}
};
正如我们所看到的,在几行代码(LOC
它可以与 JPA 实体一起使用吗?
只要实体具有公共 getter 和 setter,它就可以。与 JPA 实现不同,此表模型不支持反射,因此我们必须使用类的公共接口来访问对象属性来实现getValueAt()
and setValueAt()
方法。
它可以与 JDBC 一起使用吗?
不,没有。我们必须将结果集包装到域类中并使用类提供的接口,如上所述。
它可以与 Java 默认类一起使用吗?
是的,它确实。再次使用类提供的接口。例如,让我们采取java.io.File
类中,我们可以有下面的表模型实现:
String[] header = new String[] {
"Name",
"Full path",
"Last modified",
"Read",
"Write",
"Execute",
"Hidden",
"Directory"
};
DataObjectTableModel<File> model = new DataObjectTableModel<File>(Arrays.asList(header)) {
@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0: return String.class;
case 1: return String.class;
case 2: return Date.class;
case 3: return Boolean.class;
case 4: return Boolean.class;
case 5: return Boolean.class;
case 6: return Boolean.class;
case 7: return Boolean.class;
default: throw new ArrayIndexOutOfBoundsException(columnIndex);
}
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
File file = getDataObject(rowIndex);
switch (columnIndex) {
case 0: return file.getName();
case 1: return file.getAbsolutePath();
case 2: return new Date(file.lastModified());
case 3: return file.canRead();
case 4: return file.canWrite();
case 5: return file.canExecute();
case 6: return file.isHidden();
case 7: return file.isDirectory();
default: throw new ArrayIndexOutOfBoundsException(columnIndex);
}
}
};