18.3 TableMode接口
现在我们已经了解了JTable组件的基础,现在我们可以了解其内部是如何管理数据元素的了。他是借助于实现了TableModel接口的类来完成的。
TableModel接口定义了JTable查询列头与表格单元值,并且当表格可编辑时修改单元值所需要的框架。其定义如下:
public interface TableModel { // Listeners public void addTableModelListener(TableModelListener l); public void removeTableModelListener(TableModelListener l); // Properties public int getColumnCount(); public int getRowCount(); // Other methods public Class getColumnClass(int columnIndex); public String getColumnName(int columnIndex); public Object getValueAt(int rowIndex, int columnIndex); public boolean isCellEditable(int rowIndex, int columnIndex); public void setValueAt(Object vValue, int rowIndex, int columnIndex); }
18.3.1 AbstractTableModel类
AbstractTableModel类提供了TableModel接口的基本实现。他管理TableModelListener列表以及一些TableModel方法的默认实现。当我们派生这个类时,我们所需要提供的就是实际列与行的计数以及表格模型中的特定值(getValueAt())。列名默认为为如A,B,C,...,Z,AA,BB之类的标签,并且数据模型是只读的,除非isCellEditable()被重写。
如果我们派生AbstractTableModel并且使得数据模型是可编辑的,那么我们就要负责调用AbstractTableModel中的fireXXX()方法来保证当数据模型发生变化时TableModelListener对象都会得到通知:
public void fireTableCellUpdated(int row, int column); public void fireTableChanged(TableModelEvent e); public void fireTableDataChanged(); public void fireTableRowsDeleted(int firstRow, int lastRow); public void fireTableRowsInserted(int firstRow, int lastRow); public void fireTableRowsUpdated(int firstRow, int lastRow); public void fireTableStructureChanged();
当我们需要创建一个JTable时,为了重用已有的数据结构而派生AbstractTableModel并不常见。这个数据结构通常是来自JDBC查询的结果,但是并没有限制必须是这种情况。为了进行演示,下面的匿名类定义显示了我们如何将一个数据看作一个AbstractTableModel:
TableModel model = new AbstractTableModel() { Object rowData[][] = { {"one", "ichi"}, {"two", "ni"}, {"three", "san"}, {"four", "shi"}, {"five", "go"}, {"six", "roku"}, {"seven", "shichi"}, {"eight", "hachi"}, {"nine", "kyu"}, {"ten", "ju"} }; Object columnNames[] = {"English", "Japanese"}; public String getColumnName(int column) { return columnNames[column].toString(); } public int getRowCount() { return rowData.length; } public int getColumnCount() { return columnNames.length; } public Object getValueAt(int row, int col) { return rowData[row][col]; } }; JTable table = new JTable(model); JScrollPane scrollPane = new JScrollPane(table);
指定固定的JTable列
现在我们已经了解了TableModel与AbstractTableModel是如何描述数据的基础了,现在我们可以创建一个JTable了,其中一些列是固定的,而另一些不是。要创建不滚动的列,我们需要将第二个表格放在JScrollPane的行头视图中。然后,当用户垂直滚动表格时,两个表格就会保持同步。两个表格需要共享他们的ListSelectionModel。 这样,当一个表格中的一行被选中时,另一个表格中的行也会自动被选中。图18-9显示了具有一个固定列与四个滚动列的表格。
生成图18-9示例的源代码显示在列表18-4中。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; public class FixedTable { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rowData[][] = { {"1", "one", "ichi", "un", "I", "\u4E00"}, {"2", "two", "ni", "deux", "II", "\u4E8C"}, {"3", "three", "san", "trois", "III", "\u4E09"}, {"4", "four", "shi", "quatre", "IV", "\u56DB"}, {"5", "five", "go", "cinq", "V", "\u4E94"}, {"6", "six", "roku", "treiza", "VI", "\u516D"}, {"7", "seven", "shichi", "sept", "VII", "\u4E03"}, {"8", "eight", "hachi", "huit", "VIII", "\u516B"}, {"9", "nine", "kyu", "neur", "IX", "\u4E5D"}, {"10", "ten", "ju", "dix", "X", "\u5341"} }; final String columnNames[] = { "#", "English", "Japanese", "French", "Roman", "Kanji" }; final TableModel fixedColumnModel = new AbstractTableModel() { public int getColumnCount() { return 1; } public String getColumnName(int column) { return columnNames[column]; } public int getRowCount() { return rowData.length; } public Object getValueAt(int row, int column) { return rowData[row][column]; } }; final TableModel mainModel = new AbstractTableModel() { public int getColumnCount() { return columnNames.length-1; } public String getColumnName(int column) { return columnNames[column+1]; } public int getRowCount() { return rowData.length; } public Object getValueAt(int row, int column) { return rowData[row][column+1]; } }; Runnable runner = new Runnable() { public void run() { JTable fixedTable = new JTable(fixedColumnModel); fixedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JTable mainTable = new JTable(mainModel); mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); ListSelectionModel model = fixedTable.getSelectionModel(); mainTable.setSelectionModel(model); JScrollPane scrollPane = new JScrollPane(mainTable); Dimension fixedSize = fixedTable.getPreferredSize(); JViewport viewport = new JViewport(); viewport.setView(fixedTable); viewport.setPreferredSize(fixedSize); viewport.setMaximumSize(fixedSize); scrollPane.setCorner(JScrollPane.UPPER_LEFT_CORNER, fixedTable.getTableHeader()); scrollPane.setRowHeaderView(viewport); JFrame frame = new JFrame("Fixed Column Table"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
激活默认的表格单元渲染器
在前面的章节中,我们提到,JTable为Date与Number类提供了默认渲染器。现在我们了解一下AbstractTableModel类并且了解如何激活这些渲染器。
TableModel的public Class getColumnClass(int column)方法为数据模型中的列返回类类型。如果JTable类为这个特定类安装了特殊的渲染器,则会使用这个渲染器来显示这个类。默认情况下,TableModel的AbstractTableModel(以及DefaultTableModel)实现会为所有的事情返回Object.class。AbstractTableModel类并不会尝试聪明的猜测什么在列中。然而,如果我们知道数据模型中的特定列总是数字,日期或是其他的类,我们可以使得数据模型返回类类型。这就会允许JTable尝试更为聪明并且使用更好的渲染器。
表18-5显示了JTable的预安装的渲染器。例如,如果我们有一个满是数字的表格或是有一个数字列,我们可以重写getColumnClass()来相应的列返回Number.class;我们的数字将会右对齐而不是左对齐。对于日期,为Date类使用默认渲染器会产生更好的观感以及本地化输出。
图18-10显示了激活渲染器之前与之后的样子。
我们可以选择为列硬编码类名或是使得getColumnClass()方法通用并且在列元素上调用getClass()方法。将下面的代码添加到AbstractTableModel实现中将会使得JTable使用其默认渲染器。这个实现假定特定列的所有实体是同一个类类型。
public Class getColumnClass(int column) { return (getValueAt(0, column).getClass()); }
18.3.2 DefaultTableModel类
DefaultTableModel类是AbstractTableModel的子类,他为存储提供了自己的Vector数据。数据模型中的所有内容在内部都是存储在向量中的即使当数据初始时是数组的一部分也是如此。换句话说,如果我们已经将我们的数据放在一个适当的数据结构中,则不要使用DefaultTableModel。创建一个使用该数据结构的AbstractTableModel,而不要使用DefaultTableModel为我们转换数据结构。
创建DefaultTableModel
有六个构造函数可以用来创建DefaultTableModel:
public DefaultTableModel() TableModel model = new DefaultTableModel() public DefaultTableModel(int rows, int columns) TableModel model = new DefaultTableModel(2, 3) public DefaultTableModel(Object rowData[][], Object columnNames[]) Object rowData[][] = {{"Row1-Column1", "Row1-Column2", "Row1-Column3"}, {"Row2-Column1", "Row2-Column2", "Row2-Column3"}}; Object columnNames[] = {"Column One", "Column Two", "Column Three"}; TableModel model = new DefaultTableModel(rowData, columnNames); public DefaultTableModel(Vector rowData, Vector columnNames) Vector rowOne = new Vector(); rowOne.addElement("Row1-Column1"); rowOne.addElement("Row1-Column2"); rowOne.addElement("Row1-Column3"); Vector rowTwo = new Vector(); rowTwo.addElement("Row2-Column1"); rowTwo.addElement("Row2-Column2"); rowTwo.addElement("Row2-Column3"); Vector rowData = new Vector(); rowData.addElement(rowOne); rowData.addElement(rowTwo); Vector columnNames = new Vector(); columnNames.addElement("Column One"); columnNames.addElement("Column Two"); columnNames.addElement("Column Three"); TableModel model = new DefaultTableModel(rowData, columnNames); public DefaultTableModel(Object columnNames[], int rows) TableModel model = new DefaultTableModel(columnNames, 2); public DefaultTableModel(Vector columnNames, int rows) TableModel model = new DefaultTableModel(columnNames, 2);
其中四个构造函数直接映射到JTable构造函数,而其余的两个则允许我们由一个列头集合创建一个具有固定行数的空表格。一旦我们创建了DefaultTableModel,我们就可以将传递给JTable构造函数来创建实际的表格,然后将这个表格放在JScrollPane中。
填充DefaultTableModel
如果我们选择使用DefaultTableModel,我们必须使用JTable要显示的数据来进行填充。除了填充数据结构的基本例程以外,还有一些移除数据或是替换整个内容的额外方法:
下面的方法允许我们添加列:
public void addColumn(Object columnName); public void addColumn(Object columnName, Vector columnData); public void addColumn(Object columnName, Object columnData[ ]);
使用下面的方法来添加行:
public void addRow(Object rowData[ ]); public void addRow(Vector rowData);
下面的方法可以插入行:
public void insertRow(int row, Object rowData[ ]); public void insertRow(int row, Vector rowData);
这个方法可以移除行:
public void removeRow( int row);
最后,我们可以使用下面的方法来替换内容:
public void setDataVector(Object newData[ ][ ], Object columnNames[ ]); public void setDataVector(Vector newData, Vector columnNames);
DefaultTableModel属性
除了由AbstractTableModel继承的rowCount与columnCount属性以外,DefaultTableModel还有两个其他的属性,如表18-6所示。设置rowCount属性可以使得我们按照我们的意愿扩大或是缩小表格尺寸。如果我们正在扩展模型,其他的行会保持为空。
创建一个稀疏的表格模型
默认的表格模型实现用于填满数据的表格,而不是用于由大多数空表格单元的组成的数据表。当表格中的单元大部分为空时,DefaultTableModel的默认数据结构就会学浪费大量的空间。以为每一个位置创建一个Point为代价,我们可以创建一个使用HashMap的稀疏表格模型。列表18-5演示了这种实现。
package swingstudy.ch18; import java.awt.Point; import java.util.HashMap; import java.util.Map; import javax.swing.table.AbstractTableModel; public class SparseTableModel extends AbstractTableModel { private Map<Point, Object> lookup; private final int rows; private final int columns; private final String headers[]; public SparseTableModel(int rows, String columnHeaders[]) { if((rows<0) || (columnHeaders == null)) { throw new IllegalArgumentException("Invalida row count/columnHeaders"); } this.rows = rows; this.columns = columnHeaders.length; headers = columnHeaders; lookup = new HashMap<Point, Object>(); } @Override public int getRowCount() { // TODO Auto-generated method stub return rows; } @Override public int getColumnCount() { // TODO Auto-generated method stub return columns; } public String getColumnName(int column) { return headers[column]; } @Override public Object getValueAt(int row, int column) { // TODO Auto-generated method stub return lookup.get(new Point(row, column)); } public void setValueAt(Object value, int row, int column) { if((rows<0) || (columns<0)) { throw new IllegalArgumentException("Invalid row/column setting"); } if((row<rows) && (column<columns)) { lookup.put(new Point(row, column), value); } } }
测试这个示例涉及到创建并填充模型,如下面的代码所示:
String headers[] = { "English", "Japanese"}; TableModel model = new SparseTableModel(10, headers); JTable table = new JTable(model); model.setValueAt("one", 0, 0); model.setValueAt("ten", 9, 0); model.setValueAt("roku - \ u516D", 5, 1); model.setValueAt("hachi - \ u516B", 8, 1);
使用TableModelListener监听JTable事件
如果我们需要动态更新我们的表格数据,我们可以使用TableModelListener来确定数据何时发生变化。这个接口由一个可以告诉我们表格数据何时发生变化的方法构成。
public interface TableModelListener extends EventListener { public void tableChanged(TableModelEvent e); }
在TableModelListener得到通知以后,我们可以向TableModelEvent查询所发生的事件的类型以及受到影响的行与列的范围。表18-7显示了我们可以查询的TableModelEvent的属性。
事件类型可以是TableModeleEvent三个类型常量中的一个:INSERT, UPDATE或是DELETE。
如果TableModelEvent的column属性设置为ALL_COLUMNS,那么数据模型中所有的列都会受到影响。如果firstRow属性为HEADER_ROW,则意味着表格头发生了变化。
18.3.4 排序JTable元素
JTable组件并没有内建的排序支持。然而,却经常需要这一特性。排序并不需要改变数据模型,但是他却需要改变JTable所具有的数据模型视图。这种改变类型是通过装饰者模式来描述的,在这种模式中我们维护到数据的相同的API,但是向视图添加排序功能。装饰者设计模式的设计如下:
- Component:组件定义了将要装饰的服务接口。
- ConcreteComponent:具体组件是将要装饰的对象。
- Decorator:装饰者是到具体组件的一个抽象封装;他维护服务接口。
- ConcreteDecorator(s)[A,B,C,...]:具体装饰者对象通过添加装饰功能扩展装饰者,然而维护相同的编程接口。他们将服务请求重定向到由抽象超类所指向的具体组件。
注意,java.io包的流是装饰者模式的示例。各种过滤器流向基本的流类添加功能并且维护相同的访问API。
在表格排序这个特定例子中,只需要Component,ConcreteComponent与Decorator,因为只有一个具体装饰者。Component是TableModel接口,ConcreteComponent是实际的模型,而Decorator是排序模型。
为了排序,我们需要维护一个真实数据到排序数据的一个映射。由用户接口,我们必须允许用户选择一个列头标签来激活特定列的排序。
要使用排序功能,我们告诉自定义TableSorter类关于我们数据模型的情况,装饰这个模型,并且由装饰模型而不是原始模型创建一个JTable。要通过点击列头标签来激活排序,我们需要调用TableHeaderSorter类的install()方法,如下面的TableSorter类的源码所示:
TableSorter sorter = new TableSorter(model); JTable table = new JTable(sorter); TableHeaderSorter.install(sorter, table);
TableSorter类的主要源码显示在列表18-6中。他扩展了TableMap类,该类显示在列表18-7中。TableSorter类是所有动作所在的位置。该类执行排序并且通知其他类数据已经发生变化。
package swingstudy.ch18; import java.sql.Date; import java.util.Vector; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; public class TableSorter extends TableMap implements TableModelListener { int indexes[] = new int[0]; Vector sortingColumns = new Vector(); boolean ascending = true; public TableSorter() { } public TableSorter(TableModel model) { setModel(model); } public void setModel(TableModel model) { super.setModel(model); reallocateIndexes(); sortByColumn(0); fireTableDataChanged(); } public int compareRowsByColumn(int row1, int row2, int column) { Class type = model.getColumnClass(column); TableModel data = model; // check for nulls Object o1 = data.getValueAt(row1, column); Object o2 = data.getValueAt(row2, column); // if both values are null return 0 if(o1 == null && o2 == null) { return 0; } else if(o1 == null) { // define null less than everything return -1; } else if(o2 == null) { return 1; } if(type.getSuperclass() == Number.class) { Number n1 = (Number)data.getValueAt(row1, column); double d1 = n1.doubleValue(); Number n2 = (Number)data.getValueAt(row1, column); double d2 = n2.doubleValue(); if(d1<d2) { return -1; } else if(d1>d2) { return 1; } else { return 0; } } else if(type == String.class) { String s1 = (String)data.getValueAt(row1, column); String s2 = (String)data.getValueAt(row2, column); int result = s1.compareTo(s2); if(result < 0) return -1; else if(result > 0) return 1; else return 0; } else if(type == java.util.Date.class) { Date d1 = (Date)data.getValueAt(row1, column); long n1 = d1.getTime(); Date d2 = (Date)data.getValueAt(row2, column); long n2 = d2.getTime(); if(n1 < n2) return -1; else if(n1 > n2) return 1; else return 0; } else if(type == Boolean.class) { Boolean bool1 = (Boolean)data.getValueAt(row1, column); boolean b1 = bool1.booleanValue(); Boolean bool2 = (Boolean)data.getValueAt(row2, column); boolean b2 = bool2.booleanValue(); if(b1 == b2) { return 0; } else if(b1) // define false < true return 1; else return -1; } else { Object v1 = data.getValueAt(row1, column); String s1 = v1.toString(); Object v2 = data.getValueAt(row2, column); String s2 = v2.toString(); int result = s1.compareTo(s2); if(result < 0) return -1; else if(result > 0) return 1; else return 0; } } public int compare(int row1, int row2) { for(int level=0, n=sortingColumns.size(); level<n;level++) { Integer column = (Integer)sortingColumns.elementAt(level); int result = compareRowsByColumn(row1, row2, column.intValue()); if(result != 0) { return (ascending ? result : -result); } } return 0; } public void reallocateIndexes() { int rowCount = model.getRowCount(); indexes = new int[rowCount]; for(int row=0; row<rowCount; row++) { indexes[row] = row; } } @Override public void tableChanged(TableModelEvent e) { // TODO Auto-generated method stub super.tableChanged(e); reallocateIndexes(); sortByColumn(0); fireTableStructureChanged(); } public void checkModel() { if(indexes.length != model.getRowCount()) { System.err.println("Sorter not informed of a change in model."); } } public void sort() { checkModel(); shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length); fireTableDataChanged(); } public void shuttlesort(int from[], int to[], int low, int high) { if(high-low<2) { return ; } int middle = (low+high)/2; shuttlesort(to, from, low, middle); shuttlesort(to, from, middle, high); int p = low; int q = middle; for(int i=low; i<high; i++) { if(q>=high || (p<middle && compare(from[p], from[q]) <= 0)) { to[i] = from[p++]; } else { to[i] = from[q++]; } } } private void swap(int first, int second) { int temp = indexes[first]; indexes[first] = indexes[second]; indexes[second] = temp; } public Object getValueAt(int row, int column) { checkModel(); return model.getValueAt(indexes[row], column); } public void setValueAt(Object aValue, int row, int column) { checkModel(); model.setValueAt(aValue, row, column); } public void sortByColumn(int column) { sortByColumn(column, true); } public void sortByColumn(int column, boolean ascending) { this.ascending = ascending; sortingColumns.removeAllElements(); sortingColumns.addElement(new Integer(column)); sort(); super.tableChanged(new TableModelEvent(this)); } }
显示在列表18-7中的TableMap类作为一个代理,将调用传递给相应的TableModel类。他是列表18-6中的TableSorter类的超类。
package swingstudy.ch18; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableModel; public class TableMap extends AbstractTableModel implements TableModelListener { TableModel model; public TableModel getModel() { return model; } public void setModel(TableModel model) { if(this.model != null) { this.model.removeTableModelListener(this); } this.model = model; if(this.model != null) { this.model.addTableModelListener(this); } } public Class getcolumnClass(int column) { return model.getColumnClass(column); } @Override public int getRowCount() { // TODO Auto-generated method stub return ((model == null)? 0 : model.getRowCount()); } @Override public int getColumnCount() { // TODO Auto-generated method stub return ((model == null)? 0 :model.getColumnCount()); } public String getColumnName(int column) { return model.getColumnName(column); } @Override public Object getValueAt(int rowIndex, int columnIndex) { // TODO Auto-generated method stub return model.getValueAt(rowIndex, columnIndex); } public void setValueAt(Object value, int row, int column) { model.setValueAt(value, row, column); } public boolean isCellEditable(int row, int column) { return model.isCellEditable(row, column); } @Override public void tableChanged(TableModelEvent e) { // TODO Auto-generated method stub fireTableChanged(e); } }
排序例程的安装需要MouseListener的注册,如列表18-8所示,从而表格头中的选择会触发排序处理。通常的鼠标点击是升序排列;Shift点击为降序排列。
package swingstudy.ch18; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JTable; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumnModel; public class TableHeaderSorter extends MouseAdapter { private TableSorter sorter; private JTable table; private TableHeaderSorter() { } public static void install(TableSorter sorter, JTable table) { TableHeaderSorter tableHeaderSorter = new TableHeaderSorter(); tableHeaderSorter.sorter = sorter; tableHeaderSorter.table = table; JTableHeader tableHeader = table.getTableHeader(); tableHeader.addMouseListener(tableHeaderSorter); } public void mouseClicked(MouseEvent event) { TableColumnModel columnModel = table.getColumnModel(); int viewColumn = columnModel.getColumnIndexAtX(event.getX()); int column = table.convertColumnIndexToModel(viewColumn); if(event.getClickCount() == 1 && column != -1) { System.out.println("Sorting ..."); int shiftPressed = (event.getModifiers() & InputEvent.SHIFT_MASK); boolean ascending = (shiftPressed == 0); sorter.sortByColumn(column, ascending); } } }
18.4 TableColumnModel接口
TableColumnModel是那些位于背后并且不需要太多注意的接口之一。其基本作用就是管理当前通过JTable显示的列集合。除非触发去做其他一些事情,当JTable被创建时,组件就会由数据模型创建一个默认的列模型,指明显示列顺序与数据模型中的顺序相同。
当在设置JTable的数据模型之前将JTable的autoCreateColumnsFromModele属性设置为true,则TableColumnModel会自动被创建。另外,如果当前的设置需要重置,我们可以手动告诉JTable来创建默认的TableColumnModel。public void createDefaultColumnsFromModel()方法会为我们完成创建工作,并将新创建的对象赋给JTable的TableColumnModel。
既然所有都是为我们自动完成的,我们为什么需要了解TableColumnModel呢?通常,只有当我们不喜欢默认生成的TableModel或是我们需要手动移动一些内容时,我们需要使用这个接口。除了维护一个TableColumn对象集合,TableColumnModel管理第二个ListSelectionModel,从而允许用户由表格中选择列与行。
在我们深入默认实现之前我们先来了解一下该接口的定义:
public interface TableColumnModel { // Listeners public void addColumnModelListener(TableColumnModelListener l); public void removeColumnModelListener(TableColumnModelListener l); // Properties public int getColumnCount(); public int getColumnMargin(); public void setColumnMargin(int newMargin); public Enumeration getColumns(); public boolean getColumnSelectionAllowed(); public void setColumnSelectionAllowed(boolean flag); public int getSelectedColumnCount(); public int[ ] getSelectedColumns(); public ListSelectionModel getSelectionModel(); public void setSelectionModel(ListSelectionModel newModel); public int getTotalColumnWidth(); // Other methods public void addColumn(TableColumn aColumn); public TableColumn getColumn(int columnIndex); public int getColumnIndex(Object columnIdentifier); public int getColumnIndexAtX(int xPosition); public void moveColumn(int columnIndex, int newIndex); public void removeColumn(TableColumn column); }
18.4.1 DefaultTableColumnModel类
DefaultTableColumnModel类定义了系统所用的TableColumnModel接口的实现。他在JTable内通过跟踪空白,宽度,选择与数量来描述TableColumn对象的一般外观。表18-8显示了用于访问DefaultTableColumnModel设置的9个属性。
除了类属性,我们可以使用下面的方法通过TableColumn类来添加,移除与移动列,我们会在稍后进行讨论。
public void addColumn(TableColumn newColumn); public void removeColumn(TableColumn oldColumn); public void moveColumn(int currentIndex, int newIndex);
18.4.2 使用TableColumnModelListener监听JTable事件
也许我们通过TableColumnModel要做的事情之一就是使用TableColumnModelListener来监听TableColumnModelEvent对象。监听器会得到列的添加,移除,移动或是选择,或是列空白变化的通知,如前面的接口定义所示。注意,当事件发生时不同的方法并不同有全部接收TableColumnModelEvent对象。
public interface TableColumnModelListener extends EventListener { public void columnAdded(TableColumnModelEvent e); public void columnMarginChanged(ChangeEvent e); public void columnMoved(TableColumnModelEvent e); public void columnRemoved(TableColumnModelEvent e); public void columnSelectionChanged(ListSelectionEvent e); }
因为监听器定义标明了事件类型,TableColumnModelEvent定义只定义了变化所影响的列的范围,如表18-9所示。
要查看TableColumnModelListener的演示,我们可以向我们的TableColumnModel对象关联一个监听器:
TableColumnModel columnModel = table.getColumnModel(); columnModel.addColumnModelListener(...);
在列表18-9中我们可以看到这样的监听器。他除了输出信息以外并没有做其他的事情。然而我们可以用其来确定不同事情的发生。
TableColumnModelListener tableColumnModelListener = new TableColumnModelListener() { public void columnAdded(TableColumnModelEvent e) { System.out.println("Added"); } public void columnMarginChanged(ChangeEvent e) { System.out.println("Margin"); } public void columnMoved(TableColumnModelEvent e) { System.out.println("Moved"); } public void columnRemoved(TableColumnModelEvent e) { System.out.println("Removed"); } public void columnSelectionChanged(ListSelectionEvent e) { System.out.println("Selected"); } };
当然我们需要编写一些代码来引出特定的事件。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; public class ColumnModelSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rows[][] = { {"one", "ichi - \u4E00"}, {"two", "ni - \u4E8C"}, {"three", "san - \u4E09"}, {"four", "shi - \u56DB"}, {"five", "go - \u4E94"}, {"six", "roku - \u516D"}, {"seven", "shichi - \u4E03"}, {"eight", "kachi - \u516B"}, {"nine", "kyu - \u4E5D"}, {"ten", "ju - \u5341"} }; final Object headers[] = {"English", "Japanese"}; Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Scrollless Table"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTable table = new JTable(rows, headers); TableColumnModelListener tableColumnModelListener = new TableColumnModelListener() { public void columnAdded(TableColumnModelEvent e) { System.out.println("Added"); } public void columnMarginChanged(ChangeEvent e) { System.out.println("Margin"); } public void columnMoved(TableColumnModelEvent e) { System.out.println("Moved"); } public void columnRemoved(TableColumnModelEvent e) { System.out.println("Removed"); } public void columnSelectionChanged(ListSelectionEvent e) { System.out.println("Selection Changed"); } }; TableColumnModel columnModel = table.getColumnModel(); columnModel.addColumnModelListener(tableColumnModelListener); columnModel.setColumnMargin(12); TableColumn column = new TableColumn(1); columnModel.addColumn(column); JScrollPane pane = new JScrollPane(table); frame.add(pane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
18.4.3 TableColumn类
TableColumn是另一个很重要的幕后类。Swing表格由一个列集合构成,而列由表格单元构成。这个列中的每一个是通过TableColumn实例来描述的。TableColumn类的每一个实例存储相应的编辑器,渲染器,名字与尺寸信息。然后TableColumn对象被组合进TableColumnModel来构成当前要由JTable显示的列集合。在这里有一个有用的技巧,如果我们不希望某一列被显示,我们就将其TableColumn由TableColumnModel中移除,但是将保留在TableModel中。
创建TableColumn
如果我们选择自己来创建我们的TableColumn,我们可以使用以下四个构造函数中的一个。他们是通过添加构造函数参数来级联的。
public TableColumn() TableColumn column = new TableColumn() public TableColumn(int modelIndex) TableColumn column = new TableColumn(2) public TableColumn(int modelIndex, int width) TableColumn column = new TableColumn(2, 25) public TableColumn(int modelIndex, int width, TableCellRenderer renderer, TableCellEditor editor) TableColumn column = new TableColumn(2, 25, aRenderer, aEditor)
如果没有参数,例如列表中的第一个构造函数,我们就会获得一个空列,其具有默认宽度(75像素),默认编辑器,以及默认渲染器。modelIndex参数允许我们指定我们希望TableColumn在JTable中显示TableModel中的哪一列。如果我们不喜欢默认的设置,我们也可以指定宽度,渲染器,或是编辑器。如是我们喜欢其中的一个而不喜欢其他的,我们也可以为渲染器或是编辑器指定null。
TableColumn属性
列表18-10列出了TableColumn的12个属性。这些属性可以使得我们在初始的构造参数集合以外自定义列。大多数时候,我们可以基于TableModel配置所有的事情。然而,我们仍然可以通过TableColumn类来自定义单个列。除了监听器列表,所有的属性都是绑定的。
注意,如果列所有的默认头渲染器headerRenderer为null:TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();则默认渲染器不会由getHeaderRenderer()方法返回。
在列头中使用图标
默认情况下,表格的头渲染器显示文本或是HTML。尽管我们可以使用HTML获得多行文本或是图片,但是有时我们希望在头中显示通常的Icon对象,如图18-11中的示例所示。要实现这一目的,我们必须修改头的渲染器。头渲染器只是另一个TableCellRenderer。
要创建一个可以显示图片的灵活渲染器,要使得渲染器将value数据看作为JLabel,而不是使用value来填充JLabel。列表18-11显示一个这样的渲染器,用于创建图18-11中的程序。
package swingstudy.ch18; import java.awt.Component; import javax.swing.JComponent; import javax.swing.JTable; import javax.swing.table.TableCellRenderer; public class JComponentTableCellRenderer implements TableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // TODO Auto-generated method stub return (JComponent)value; } }
图18-11显示了这个渲染器如何使用DiamondIcon显示Icon。示例程序的源码显示在列表18-12中。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.Color; import java.awt.EventQueue; import javax.swing.Icon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import swingstudy.ch04.DiamondIcon; public class LabelHeaderSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rows[][] = { {"one", "ichi - \u4E00"}, {"two", "ni - \u4E8C"}, {"three", "san - \u4E09"}, {"four", "shi - \u56DB"}, {"five", "go - \u4E94"}, {"six", "roku - \u516D"}, {"seven", "shichi - \u4E03"}, {"eight", "kachi - \u516B"}, {"nine", "kyu - \u4E5D"}, {"ten", "ju - \u5341"} }; Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Label Header"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); String headers[] = {"English", "Japanese"}; JTable table = new JTable(rows, headers); JScrollPane scrollPane = new JScrollPane(table); Icon redIcon = new DiamondIcon(Color.RED); Icon blueIcon = new DiamondIcon(Color.BLUE); Border headerBorder = UIManager.getBorder("TableHeader.cellBorder"); JLabel blueLabel = new JLabel(headers[0], blueIcon, JLabel.CENTER); blueLabel.setBorder(headerBorder); JLabel redLabel = new JLabel(headers[1], redIcon, JLabel.CENTER); redLabel.setBorder(headerBorder); TableCellRenderer renderer = new JComponentTableCellRenderer(); TableColumnModel columnModel = table.getColumnModel(); TableColumn column0 = columnModel.getColumn(0); TableColumn column1 = columnModel.getColumn(1); column0.setHeaderRenderer(renderer); column0.setHeaderValue(blueLabel); column1.setHeaderRenderer(renderer); column1.setHeaderValue(redLabel); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }