目录
- 如何使用Table
- (1)创建一个简单的表格
- (2)向容器添加表格
- (3)改变每每一列的宽度
- (4)用户选择
- (5)创建表格模型
- (6)监听数据改变
- (7)点燃数据改变事件
- (8)概念:编辑器和渲染器(Editors and Renderers)
- (9)使用自定义渲染器
- (10)为单元格指定提示工具
- (11)为列头指定工具集
- (12)排序和过滤
- (13)使用combo box作为编辑器
- (14)使用其他编辑器
- (15)使用编辑器验证用户的文本输入
- (16)打印
- (17)例子列表
如何使用Table
利用 JTable 类,可以以表格的形式展示数据,可设置允许用户编辑数据。JTable 本身不拥有或者缓存数据;它只是数据的视图。这里有一个放在滚动面板上的典型表格:
本文展示如何完成一些常见的表格相关的任务:包括以下部分:
(1)创建一个简单的表格
(2)向容器添加表格
(3)改变每每一栏的宽度
(4)用户选择
(5)创建表格模型
(6)监听数据改变
(7)点燃数据改变事件
(8)概念:编辑器和渲染器
(9)使用自定义渲染器
(10)为单元格指定提示工具
(11)为列头指定提示工具
(12)排序和过滤
(13)使用组合框作为编辑器
(14)使用其他编辑器
(15)使用编辑器验证用户的文本输入
(16)打印
(17)例子列表
(1)创建一个简单的表格
SimpleTableDemo.java
中的表格在一个字符串数组中声明各个列名
- String[] columnNames = {"First Name",
- "Last Name",
- "Sport",
- "# of Years",
- "Vegetarian"};
数据初始化并存储在二维数组:
- Object[][] data = {
- {"Kathy", "Smith",
- "Snowboarding", new Integer(5), new Boolean(false)},
- {"John", "Doe",
- "Rowing", new Integer(3), new Boolean(true)},
- {"Sue", "Black",
- "Knitting", new Integer(2), new Boolean(false)},
- {"Jane", "White",
- "Speed reading", new Integer(20), new Boolean(true)},
- {"Joe", "Brown",
- "Pool", new Integer(10), new Boolean(false)}
- };
接着表格使用这些数据和列名构造一个表格:
- JTable table = new JTable(data, columnNames);
有两个接收数据的 JTable 构造器:
- JTable(Object[][] rowData, Object[] columnNames)
- JTable(Vector rowData, Vector columNames)
这些构造函数的好处是容易实现,而缺点是:
- 他们自动设置每个单元格为可编辑
- 数据类型都视为一样的(字符串类型)。例如,如果表格的一列有 Boolean 数据,表格用单选框来展示这个数据。可是,如果你用上面两种构造,你的 Boolean 数据将显示为字符串,就像前面表格截图中的 Vegetarian 一列一样。
- 它们要求你把所有表格数据放入一个数组或者vector,这些数据结构可能不适合某些数据类型。例如,如果你实体化一组数据库对象,比起拷贝所有值放入数组和vector,你可能仅仅想要直接查询这些对象的值。
如果你想避免这些限制,你需要实现你自己的表格模型,见“(5)创建表格模型”。
(2)向容器添加表格
这里有一段创建滚动面板作为表格容器的常规代码:
- JScrollPane scrollPane = new JScrollPane(table);
- table.setFillsViewportHeight(true);
这两行代码实现了:表格对象的引用作为 JScrollPane 构造函数的参数,创建一个容纳table的容器,table添加到容器中;JTable.setFillsViewportHeight 方法设置了 fillsViewportHeight 属性。当这个属性为 true 时,表格会占据容器整个高度,即便表格没有足够的行去使用这些垂直空间。这使得表格更容易实现拖拉操作。
滚动面板自动把表格头放置在视窗的顶端。当表格数据垂直滚动时,列名保持在视窗顶端可视。
如果你想要使用一个没有滚动面板的表格,你必须获得表格头组件,然后自己放置它,例如:
- container.setLayout(new BorderLayout());
- container.add(table.getTableHeader(), BorderLayout.PAGE_START);
- container.add(table, BorderLayout.CENTER);
(3)改变每每一列的宽度
默认情况下,表格所有列等宽,切这些列自动填满整个表格的宽度。当表格变宽或者变窄时(通常是用户调整包含表格的窗口大小),所有的列宽自动调整到适当宽度。
当用户通过拖动列头的右边界来调整某一列的宽度时,要么别的列的宽度会受到影响而改变,要么整个表格的宽度会改变。默认情况下,表格整体宽度保持不变,“拖动点“的右侧各列利用增加或减少的空间自我调整,拖动的那一列的左侧各列保持不变。
要定义各列初始化宽度,你可以对表格各列调用 setPreferredWidth 方法。可以设置各列首选宽度,和他们的相对宽度。例如,向demo增加下面代码,是的第三列比其他列更宽:
- TableColumn column = null;
- for (int i = 0; i < 5; i++) {
- column = table.getColumnModel().getColumn(i);
- if (i == 2) {
- column.setPreferredWidth(100); //third column is bigger
- } else {
- column.setPreferredWidth(50);
- }
- }
如上面代码所示,每一列代表一个 TableColumn 对象, TableColumn 提供 getter 和 setter 方法设置和获取列的最小、首选、最大宽度 和 目前宽度。 基于估计单元格内容需要的空间调整单元格宽度,查看TableRenderDemo.java
. 中的 initColumnSizes 方法。
当用户明确的调整列宽度,列的”首选宽度“就被设置为用户指定的”新的当前宽度“。不过,当表格因视窗调整而改变大小是,列的”首选宽度“不会改变。”首选宽度“的存在是用于计算新的列的宽度,来填充可用空间。
你可以通过调用 setAutoResizeMode 改变一个表格的调整模式。
(4)用户选择
默认配置情况下,一个表格支持选择一行或多行。用户可以选择一组连续的或不连续的行。用户最后指示的那个单元格,在 Metal 样式中,会被outlined(轮廓虚线)。这个单元格被称为 ”lead selection“(导联选择(器, 钮));有时候也称为 ”聚焦单元格“ 或 ”当前单元格“。
用户使用鼠标键盘实现选择,选择的方式描述如下
操作鼠标动作
键盘动作
选择单行 | 点击 | 向上或向下 |
选中连续多行 | Shift—点击/拖拉 | Shitf-向上 或 Shift-向下 |
向选中的行集增加行/切换选择 |
Control-点击 | Control+向上或向下, 使用空格键增加当前行或切换选择. |
下面的例子程序TableSelectionDemo.java
展示了类似的表格,允许用户操纵某些 JTable 选项。还有一个文本面板记录”选择事件“。(这个demo里面的有关复选框事件处理代码写的好好)
在下面的截图中,这事默认的Metal样式,选中的行highlight,选择的单元格outline
在下面的”Selection Mode“下,有一些复选框,选择”Single Selection“。现在你只能在某一时刻选中一行,如果你选中”Single Interval Selection“,你可以选择连续的多行。
所有的”Selection Mode“下面的复选框按钮,调用 JTable.setSelectionMode
. 这个方法带一个参数,为javax.swing.ListSelectionModel
: MULTIPLE_INTERVAL_SELECTION
, SINGLE_INTERVAL_SELECTION
, andSINGLE_SELECTION
.中的一个(依次为,多行可间隔,多行无间隔,单行)
回到我们的 demo,注意,在”Selection Options“下三个复选框,每个复选框控制一个由 JTable 定义的绑定属性的boolean类型状态值:
- ”Row Selection“控制 控制 rowSelectionAllowed 属性,通过
setRowSelectionAllowed
和getRowSelectionAllowed
设置和读取。当这个绑定属性为 true (同时 columnSelectionAllowed属性为 false)时,用户可以选择行。 - ”Coolumn Selection“控制 columnSelectionAllowed 属性,通过
setColumnSelectionAllowed
和getColumnSelectionAllowed 设置和读取。当这个绑定属性为 true 时,用户可以选择单个单元格,或者呈矩阵块地
选择多个单元格
- ”Cell Selection“控制 cellSelectionEnabled,通过
setCellSelectionEnabled
andgetCellSelectionEnabled 设置和获取。当这个绑定属性为 true 是,用户可以选择单个单元格,或是以矩阵块的形式选择多个单元格。
提醒:JTable使用很简单的选择原则来管理 行 和 列 的交集,它并非设计成全面处理独立的单元格选择。(就是说,有些多单元格的选择是不被handle的,你也选不到)
如果你清空三个复选框,就没有selection了,只有lead selection表现而已。(我觉得lead selection只是形式上的选择,是一种导航观察的形式,而selection是确切选中表格中某些单元格的事实。我无法确切地解释出lead selection 和 selection的区别,我只能意会呀)
你可能注意到”Cell Selection“复选框在”multiple interval selection“选择模式中是不可用的。只是在这个demo的模式中是不被支持的。你可以在”multiple interval selection“模式中指定单元格选择,但是表格也不会产生有效的selection。
你或许还注意到,改变这”selection option“中某个选项可能影响其他选项。这是因为允许行选择和列选择,就意味着允许单元格原则。JTable自动更新三个绑定属性,以保持它们的一致性。
提醒:设置 cellSelectionEnabled 的值会附带同时设置 rowSelectionEnabled 和 columnSelectionEnabled 的效果。同样,设置后两者的值同样会影响 cellSelectionEnabled 的值。设置 row……和 cloumn……为不同值,同时设置 cell……为 false,可以测试一下。
要获得当前的selection,使用 JTable.getSelectedRows,返回一个带索引行数的数组,使用 JTable.getSelectedColumns 返回 列索引。 要获得 lead selection 的坐标,需要引用table本身的 selection model 和 table 的 column model。下面代码格式化一个包含一个lead selection的行和列的字符串:
- String.format("Lead Selection: %d, %d. ",
- table.getSelectionModel().getLeadSelectionIndex(),
- table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
使用selections产生一些时间。参考 How to Write a List Selection Listener in the Writing Event Listeners
(5)创建表格模型
每个 table 对象 使用一个 table model 对象来管理表格中真实的数据。一个 table model 对象一定要实现 TableModel 接口,如果程序没有提供一个 table model 对象,JTable自动创建一个 DefaultTableModel实例。这种关系可用下面的图来解释
SimpleTableDemo 中 JTable 的构造器如下面代码一样,创建它的 table model:
- new AbstractTableModel() {
- public String getColumnName(int col) {
- return columnNames[col].toString();
- }
- public int getRowCount() { return rowData.length; }
- public int getColumnCount() { return columnNames.length; }
- public Object getValueAt(int row, int col) {
- return rowData[row][col];
- }
- public boolean isCellEditable(int row, int col)
- { return true; }
- public void setValueAt(Object value, int row, int col) {
- rowData[row][col] = value;
- fireTableCellUpdated(row, col);
- }
- }
上面代码,简单的实现了一个 table model。通常在 AbstractTableModel 的子类中实现 table model。
你的模型可以支持 数组、vector 或 hash map类型的数据。甚至是从外资资源,如数据库中获得数据。他甚至可以在运行期间产生数据。
这个TableDemo.java
例子中的表格与前面 SimpleTableDemo 中的表格有几点区别:
- TableDemo的自定义 table model,即便它很简单,不过他可以轻松地确定数据的类型,帮助 JTable 用最好的格式展示数据。 SimpleTableDemo 自动创建的 table model, 并不知道 # of Years 一栏包括数字(需要右对齐且特殊格式),也不知道 Vegetarian 一栏包含用单选框表示的布尔值。
- 在 TableDemo 中实现的 table model 并不是让你编辑那些表示姓名的栏目,而是修改其他栏。在 SimpleTableDemo中,所有单元格都是可编辑的。
观察 TableDemo.java
的代码,粗体部分是区别于 SimpleTableDemo自动创建的 table model:
- public TableDemo() {
- ...
- JTable table = new JTable(new MyTableModel());
- ...
- }
- class MyTableModel extends AbstractTableModel {
- private String[] columnNames = ...//same as before...
- private Object[][] data = ...//same as before...
- public int getColumnCount() {
- return columnNames.length;
- }
- public int getRowCount() {
- return data.length;
- }
- public String getColumnName(int col) {
- return columnNames[col];
- }
- public Object getValueAt(int row, int col) {
- return data[row][col];
- }
- <strong>public Class getColumnClass(int c) {
- return getValueAt(0, c).getClass();
- }</strong>
- /*
- * Don't need to implement this method unless your table's
- * editable.
- */
- public boolean isCellEditable(int row, int col) {
- //Note that the data/cell address is constant,
- //no matter where the cell appears onscreen.
- <strong>if (col < 2) {
- return false;
- } else {
- return true;
- }</strong>
- }
- /*
- * Don't need to implement this method unless your table's
- * data can change.
- */
- public void setValueAt(Object value, int row, int col) {
- data[row][col] = value;
- fireTableCellUpdated(row, col);
- }
- ...
- }
(6)监听数据改变
一个 table model 可以有多个监听器,无论何时,只要表格数据被改变,都会通知这些监听器。监听器是TableModelListener
类的实例。在下面的例子代码中, SimpleTableDemo 增加了一个监听器,粗体部分是新的代码:
- <strong>import javax.swing.event.*;
- import javax.swing.table.TableModel;</strong>
- public class SimpleTableDemo ...<strong> implements TableModelListener </strong>
- {
- ...
- public SimpleTableDemo() {
- ...
- <strong> table.getModel().addTableModelListener(this);</strong>
- ...
- }
- <strong>public void tableChanged(TableModelEvent e) {
- int row = e.getFirstRow();
- int column = e.getColumn();
- TableModel model = (TableModel)e.getSource();
- String columnName = model.getColumnName(column);
- Object data = model.getValueAt(row, column);
- ...// Do something with the data...
- }</strong>
- ...
- }
(7)点燃数据改变事件
为了唤醒数据改变事件,table model一定要知道如果构造 TableModelEvent
对象。这是个复杂的过程,但是已经在 DefaultTableModel 中实现了。你可以让 JTable 使用他自己默认的 DefaultTableModel 实例,或者创建自定义的 DefaultTableModel 子类。
如果 DefaultTableModel 不适合作为自定义 table model 类的基类,考虑使用 AbstractTableModel
作为基类。这个类实现了构造 TableModelEvent 对象的简单框架。(DefaultTableModel 是该抽象类的子类)当外界改变了表格数据的时候,你的自定义类仅仅需要调用 AbstractTableModel 方法中的一个,如下:
fireTableCellUpdated |
Update of specified cell. 单元格更新 |
fireTableRowsUpdated |
Update of specified rows 行更新 |
fireTableDataChanged |
Update of entire table (data only). 表格范围内的数据更新 |
fireTableRowsInserted |
New rows inserted. 插入新行 |
fireTableRowsDeleted |
Existing rows Deleted 删除存在的行 |
fireTableStructureChanged |
Invalidate entire table, both data and structure. 使表格无效,包括数据和结构 |
(8)概念:编辑器和渲染器(Editors and Renderers)
在进行后面的学习前,你需要理解表格是如何绘制它的单元格的。你可能会认为表格中每个单元格都是一个组件,但是,考虑性能的原因,Swing的表格并不这么做。
取而代之的是,一个 single cell renderer(单一单元格绘制器)一般用来绘制所有包含同类型数据的单元格。你可以想象这个 renderer 是一个可配置的墨水打印,表格使用它将格式化的数据合适地印在每个单元格上。当用于开始编辑一个单元格的数据时, cell editor 接管这个单元格,控制单元格的编辑行为。
例如,TableDemo 的 # of Years 列中的每个单元格包含数字数据——具体是一个Integer对象。默认情况下,对于数字列,渲染器使用单个 JLabel 实例在列上的单元格绘制恰当的居右的数字。如果用户开始编辑一个单元格,则默认的单元格编辑器使用一个 居右的 JTextField 来控制单元格的编辑动作。
如何选择 render 处理某一列的单元格,表格首先会确定,对于该列,你是否已经指定了一个 renderer。如果你未指定,那么 table 会调用 table model 的 getColumnClass 方法,获得该列的单元格的数据的类型。接着,table 会将该列的数据类型与一个数据类型列表对比,该列表注册了多种 cell renderers。该表由 table 初始化,你可以向该表增加renderer。通常,table 会把下列类型放到列表中:
- Boolean——复选框
- Number——居右的label
- Double, Float——类似Number,不过 从 对象 到 文本的转化通过
NumberFormat
的实例来执行。 - Date——label,对象 到 文本 的转换通过
DateFormat
的实例来执行。 - ImageIcon,Icon——居中的label
- Object——展示了对象的字符串值的label
单元格编辑器使用类似的法则。
注意,如果让 table 自己创建它的 model,它会把 Object 作为各列的类型。为了指定更明确列类型,table model一定要定义适当的 getColumnClass 方法,像 TableDemo.java
. 中的定义那样。
记住,尽管 render 决定有多少单元格和列头被指定了它的 tool tip text(鼠标指在上面显示的提示文本),但是 render 本身不处理事件。如果你需要获得 table 内发生的事件,你使用的技术就是在下面分类的事件中做变化:
Situation How to Get EventsTo detect events from a cell that is being edited... | Use the cell editor (or register a listener on the cell editor). |
To detect row/column/cell selections and deselections... | Use a selection listener as described in Detecting User Selections. |
To detect mouse events on a column header... | Register the appropriate type of mouse listener on the table'sJTableHeader object. (See TableSorter.java for an example.) |
To detect other events... | Register the appropriate listener on the JTable object. |
(9)使用自定义渲染器
这节的内容将告诉你如何创建和指定一个 cell renderer。你可以使用 JTable 的 setDefaultRenderer 方法设置一个类型明确的 cell renderer。使用 TableColumn 的 setCellRenderer 方法,可以指定某列中的单元格使用的 renderer。你甚至可以通过创建 JTable 的子类来指定 cell-specific renderer(针对某个单元格的renderer)。
通过默认的renderer, DefaultTableCellRenderer,很容易自定义 text 和 image renderer。你只需要创建一个子类,实现 setValue 方法,这样它就会调用 setText(合适的字符串参数) 或 setIcon(合适的图像)。例如,这里给出默认的 date renderer 的实现:
- static class DateRenderer extends DefaultTableCellRenderer {
- DateFormat formatter;
- public DateRenderer() { super(); }
- public void setValue(Object value) {
- if (formatter==null) {
- formatter = DateFormat.getDateInstance();
- }
- setText((value == null) ? "" : formatter.format(value));
- }
- }
如果只是继承 DefaultTableCellRenderer 是不够的,你可以使用另外一个超类来构建 renderer。最简单的方法就是创建一个存在的空间的子类,让该子类实现 TableCellRenderer
接口。 TableCellRenderer 只要求一个方法: getTableCellRendererComponent。这个方法的实现了 建立 渲染组件 绘制具体的状态,然后返回这个组件。
在下面的 TableDialogEditDemo.java
的截图中, 用于处理列 Favorite Color一栏的单元格的 renderer,是 JLabel 的子类,名为ColorRenderer。
这里引用 ColorRenderer.java
中的代码:
- public class ColorRenderer extends JLabel
- implements TableCellRenderer {
- ...
- public ColorRenderer(boolean isBordered) {
- this.isBordered = isBordered;
- setOpaque(true); //MUST do this for background to show up.
- }
- public Component getTableCellRendererComponent(
- JTable table, Object color,
- boolean isSelected, boolean hasFocus,
- int row, int column) {
- Color newColor = (Color)color;
- setBackground(newColor);
- if (isBordered) {
- if (isSelected) {
- ...
- //selectedBorder is a solid border in the color
- //table.getSelectionBackground().
- setBorder(selectedBorder);
- } else {
- ...
- //unselectedBorder is a solid border in the color
- //table.getBackground().
- setBorder(unselectedBorder);
- }
- }
- setToolTipText(...); //Discussed in the following section
- return this;
- }
- }
下面这句代码是TableDialogEditDemo.java
中注册 ColorRender实例为 所有 Color 类数据的 默认 renderer。
- table.setDefaultRenderer(Color.class, new ColorRenderer(true));
要指定一个 cell-specific renderer,你需要定义一个 JTable 子类,覆盖 getCellRenderer 方法。例如,下面代码指定第一列第一个单元格使用一个自定义的 renderer:
- TableCellRenderer weirdRenderer = new WeirdRenderer();
- table = new JTable(...) {
- public TableCellRenderer getCellRenderer(int row, int column) {
- if ((row == 0) && (column == 0)) {
- return weirdRenderer;
- }
- // else...
- return super.getCellRenderer(row, column);
- }
- };
(10)为单元格指定提示工具
默认情况下,tool tip text(提示文本) 是否展示取决于单元格的 renderer。不过,有时候可以通过覆盖 JTable 的 getToolTipText(MouseEvent) 方法来指定 tool tip text。这节将告诉你这两种技术:
使用单元格的 renderer 增加文本提示,首先你要获得或创建一个 cell renderer。然后,在确保 这个 rendering component 是一个 JComponent后,调用 setToolTipText。(之前的ColorRender 继承了 JLabel,所以它是个JComponent,同时它也实现了TableCellRenderer,所以它是一个 rendering component)
TableRenderDemo.java
.的源代码。它对 Sport 列 增加了文本提示:
- //Set up tool tips for the sport cells.
- DefaultTableCellRenderer renderer =
- new DefaultTableCellRenderer();
- renderer.setToolTipText("Click for combo box");
- sportColumn.setCellRenderer(renderer);
虽然这个文本提示设置是静态的,但是你可以实现 依赖于单元格或者程序的 动态文本提示(前面的ColorRender中有关tool tip 的设置也是一种方法):
- 在renderer 实现的 getTableCellRendererComponent 方法中增加一点代码
- 覆盖 JTable 的 getToolTipText(MouseEvent)方法。
TableDialogEditDemo 对Color类型栏使用一个renderer,见 ColorRenderer.java
, 粗体部分为设置tool tip text 部分的代码:
- public class ColorRenderer extends JLabel
- implements TableCellRenderer {
- ...
- public Component getTableCellRendererComponent(
- JTable table, Object color,
- boolean isSelected, boolean hasFocus,
- int row, int column) {
- Color newColor = (Color)color;
- ...
- setToolTipText("RGB value: " + newColor.getRed() + ", "
- + newColor.getGreen() + ", "
- + newColor.getBlue());
- return this;
- }
- }
tool tip的效果如下:
你可以通过覆盖 JTable 的 getToolTipText(MouseEvent)方法指定 tool tip text。
这个demo设置了 Sport 和 Vegetarian栏中的单元格给出文本提示:
TableToolTipsDemo.java
中实现了Sport 和 VegeTarian 栏中单元格给出文本提示的代码如下:
- JTable table = new JTable(new MyTableModel()) {
- //Implement table cell tool tips.
- public String getToolTipText(MouseEvent e) {
- String tip = null;
- java.awt.Point p = e.getPoint();
- int rowIndex = rowAtPoint(p);
- int colIndex = columnAtPoint(p);
- int realColumnIndex = convertColumnIndexToModel(colIndex);
- if (realColumnIndex == 2) { //Sport column
- tip = "This person's favorite sport to "
- + "participate in is: "
- + getValueAt(rowIndex, colIndex);
- } else if (realColumnIndex == 4) { //Veggie column
- TableModel model = getModel();
- String firstName = (String)model.getValueAt(rowIndex,0);
- String lastName = (String)model.getValueAt(rowIndex,1);
- Boolean veggie = (Boolean)model.getValueAt(rowIndex,4);
- if (Boolean.TRUE.equals(veggie)) {
- tip = firstName + " " + lastName
- + " is a vegetarian";
- } else {
- tip = firstName + " " + lastName
- + " is not a vegetarian";
- }
- } else { //another column
- //You can omit this part if you know you don't
- //have any renderers that supply their own tool
- //tips.
- tip = super.getToolTipText(e);
- }
- return tip;
- }
- ...
- }
除了 converColumnIndexToModel 的调用意外,这段代码很容易明白。这个方法是必需的,因为用户可能在界面上移动了某些列,视图上的某列索引并不匹配 table model 中该列的索引,而数据处理是在 table model 上操作的,所以要获得对应于 table model 中该列的索引。
(11)为列头指定工具集
你可以通过设置 table 的 JTableHeader 对象,增加列头的文本提示。不同的列头常常需要不同的文本提示,你可以覆盖 table header 的 getToolTipText 方法来改变提示文本。你也可以 调用 TableColumn.setHeaderRenderer, 对 header 指定自定义的 renderer.
TableToolTipsDemo.java
中也有根据不同列显示不同列头文本提示的例子,如下图,当你将鼠标移动到后三列的列头上时,将显示提示文本。而前两列的列头未提供文本提示(名字已经充分说明这列数据,无需别的提示说明),以下是功能截图:
下面代码实现了上面的文本提示功能。创建一个 JTableHeader 子类,覆盖 getToolTipText(MouseEvent)方法,这样就能对当前列返回文本。要与 table 关联这个修订过的 header,使用 JTable 的 createDefaultTableHeader 方法,返回一个 JTableHeader 子类实例。
- protected String[] columnToolTips = {
- null, // "First Name" assumed obvious
- null, // "Last Name" assumed obvious
- "The person's favorite sport to participate in",
- "The number of years the person has played the sport",
- "If checked, the person eats no meat"};
- ...
- JTable table = new JTable(new MyTableModel()) {
- ...
- //Implement table header tool tips.
- protected JTableHeader createDefaultTableHeader() {
- return new JTableHeader(columnModel) {
- public String getToolTipText(MouseEvent e) {
- String tip = null;
- java.awt.Point p = e.getPoint();
- int index = columnModel.getColumnIndexAtX(p.x);
- int realIndex =
- columnModel.getColumn(index).getModelIndex();
- return columnToolTips[realIndex];
- }
- };
- }
- };
提醒:(有关单元格或列头文本提示)上面代码,getToolTipText(MouseEvent e) 和 createDefaultTableHeader 方法都是 JTable 的方法。用了很多匿名类的写法,要注意看仔细。
(12)排序和过滤
表格 sorting 和 filtering 是由 一个 sorter 对象管理的。获得一个 sorter 对象的最简单方法是设置 autoCreateRowSorter 绑定属性 为true:
- JTable table = new JTable();
- table.setAutoCreateRowSorter(true);
这段代码定义了一个 row sorter,他是 javax.swing.table.TableRowSorter 的实例。当用户点击某列列头时,表格会做一个 locale-specific sort。 TableSortDemo.java
,例子的截图:
你可以构造一个 TableRowSorter 实例,然后指定它为你的 table 的sorter,这样你就能获得更多的分类控制。
- TableRowSorter<TableModel> sorter
- = new TableRowSorter<TableModel>(table.getModel());
- table.setRowSorter(sorter);
TableRowSorter 使用 java.util.Comparator
(实现了该接口的)对象来排序。实现该接口,必须提供一个名为 compare 的方法,该方法定义两个了两个对象的比较值,用于排序。例如,下面代码创建了一个 Comparator,根据字符串最后一个单词来排序。(String实现了Comparable接口)
- Comparator<String> comparator = new Comparator<String>() {
- public int compare(String s1, String s2) {
- String[] strings1 = s1.split("\\s");
- String[] strings2 = s2.split("\\s");
- return strings1[strings1.length - 1]
- .compareTo(strings2[strings2.length - 1]);
- }
- };
这个例子太简单了,更具典型意义的是,实现了Comparator接口的类,同时也是 java.text.Collator
.的子类,你可以定义自己的子类,或者使用 Collator 的工厂方法,获得一个支持本地语言的 Comparator,又或是使用 java.text.RuleBasedCollator
. ,该类是 Collator 的具体子类。
为了确定某一列使用哪个 Comparator, TableRowSorter 尝试轮流使用一些规则规则。这些规则按顺序的列在下面;第一条规则为 sorter 提供了一个 Comparator,……:
- 如果通过调用 setComparator指定了一个比较器,则使用该比较器
- 如果这个 table model 反馈,某一列是由字符串组成的(TableModel.getColumnClass 返回 String.class),则使用一个基于当前本地配置的比较器用于字符串排序。
- 如果 TableModel.getColumnClass 返回的类型实现了 Comparable,则基于
Comparable.compareTo
.返的值对字符串排序。 - 如果通过调用
setStringConverter
为 table 指定一个字符串转换器,则对象转换所得的字符串值代表对象,参加基于本地语言的排序。 - 如果之前的几条规则都没被采用,则使用一个比较器,该比较器会对列上的数据对象调用 toString,然后根据返回的字符串进行基于本地语言的排序。
对于更复杂的排序,查阅TableRowSorter 和他的父类 javax.swing.DefaultRowSorter
.
调用 setSortKeys
,指定排序规则和优先排序。(有关”键“的概念,对于某一列,使用比较器排序时,无法得出某几行的顺序时,则按照键列表中的键顺序,根据这些键,其实就是列,在原来基础上再次此对这几行排序…………你懂的)这里有一个有一个根据前两列排序的例子。哪一列优先排序是取决于”排序键列表“中的“排序键”顺序。在这个例子中,第二列是第一排序键,所以根据第二列优先排序,然后再根据第一列排序:
- List <RowSorter.SortKey> sortKeys
- = new ArrayList<RowSorter.SortKey>();
- sortKeys.add(new RowSorter.SortKey(1, SortOrder.ASCENDING));
- sortKeys.add(new RowSorter.SortKey(0, SortOrder.ASCENDING));
- sorter.setSortKeys(sortKeys);
除了对结果集二次排序外,一个 table sorter 可以指定过滤器,让哪些行不显示。TableRowSorter 使用javax.swing.RowFilter
对象实现过滤功能。 RowFilter 实现了几个工厂方法,可以创建集中常用的 filter。例如regexFilter
方法返回一个基于 regular expression.(正则表达式)的 RowFilter。
在下面的例子代码中,明确的创建了一个 sorter 对象,接着可以给它指定一个 filter:
- MyTableModel model = new MyTableModel();
- sorter = new TableRowSorter<MyTableModel>(model);
- table = new JTable(model);
- table.setRowSorter(sorter);
接着你基于当前文本值进行过滤:
- private void newFilter() {
- RowFilter<MyTableModel, Object> rf = null;
- //If current expression doesn't parse, don't update.
- try {
- rf = RowFilter.regexFilter(filterText.getText(), 0);
- } catch (java.util.regex.PatternSyntaxException e) {
- return;
- }
- sorter.setRowFilter(rf);
- }
filterText 文本框的值每次改变时,newFilter() 都会被调用。try catch 防止了用户在界面的文本框中输进错误的正则表达式。
当一个 table 使用一个 sorter 时,用户看到的数据顺序可能跟 data model指定的顺序不一样,也许没有包含 data model 指定的所有行。 用户真正看到的数据被称为 “view”,拥有自己的一套坐标。 JTable 提供了方法用于转换 model 坐标 到 view 坐标——convertColumnIndexToView
and convertRowIndexToView
方法。当然也提供了model 到 view 的转换——convertColumnIndexToModel
and convertRowIndexToModel
.
提醒:每次使用 sorter 时,记得转换单元格的索引,数据真正是要在 model 上处理的。
下面代码整合这节所讨论的技术。
TableFilterDemo.java
对 TableDemo 做了一些修改,包括前面提到的一些代码。这个例子给 table 提供了一个 sorter ,使用一个文本框提供过滤的正则表达式。下面是截图是:未排序 和 未过滤,注意,model中的第三行依然为view 的第三行。
如果用户点击第二列两次,第四行就会变成第一行——这只是view的改变,model中的列顺序没改变。
如前面所描述的一样,用户向“Filter Text”文本域输入正则表达式,符合这些表达式的行将被显示。跟排序一样,过滤也是产生 view,与 mode 分离。
下面的代码,根据当前 selection 更新 status 文本框:
- table.getSelectionModel().addListSelectionListener(
- new ListSelectionListener() {
- public void valueChanged(ListSelectionEvent event) {
- int viewRow = table.getSelectedRow();
- if (viewRow < 0) {
- //Selection got filtered away.
- statusText.setText("");
- } else {
- int modelRow =
- table.convertRowIndexToModel(viewRow);
- statusText.setText(
- String.format("Selected Row in view: %d. " +
- "Selected Row in model: %d.",
- viewRow, modelRow));
- }
- }
- }
- );
(13)使用combo box作为编辑器
让 combo box 作为一个 editor 是简单的,下面粗体部分的代码,指定某列的editor 为一个 combo box
- TableColumn sportColumn = table.getColumnModel().getColumn(2);
- ...
- JComboBox comboBox = new JComboBox();
- comboBox.addItem("Snowboarding");
- comboBox.addItem("Rowing");
- comboBox.addItem("Chasing toddlers");
- comboBox.addItem("Speed reading");
- comboBox.addItem("Teaching high school");
- comboBox.addItem("None");
- sportColumn.setCellEditor(new DefaultCellEditor(comboBox));
效果图:
(14)使用其他编辑器
无论你是设置一个列的 editor (使用 TableColumn.setCellEditor 方法),还是为一个数据类型设置 eidtor (使用 JTable.setDefaultEditor 方法),你可以指定你个实现了TableCellEditor 的类作为 editor。幸运的是 DefaultCellEditor 类实现了这个借口,并且提供了参数为编辑组件(JTextField,JCheckBox 或 JComboBox)的构造函数。通常你不需要明确的指定一个check box 为 editor,因为 Boolean 类型的数据自动使用 单选框 render 和 editor。
你的单元格 ediotr 类需要定义至少两个方法—— getCellEditorValue 和 getTableCellEditorComponent。getCellEditorValue 返回单元格当前值, 该方法是 CellEditor 接口要求的; getTableCellRendererComponent 返回你想要用作编辑器的组件,该方法是 TableCellEditor 接口要求的。
下面截图包含一个表格和一个对话框,表格使用对话框间接地作为单元格编辑器。当用户开始编辑 Favorite Color 列是,呈现一个按钮(真正的cell editor),带出对话框,让用户选择不同的颜色。
下面是 ColorEditor.java
, 中的代码:
- public class ColorEditor extends AbstractCellEditor
- implements TableCellEditor,
- ActionListener {
- Color currentColor;
- JButton button;
- JColorChooser colorChooser;
- JDialog dialog;
- protected static final String EDIT = "edit";
- public ColorEditor() {
- button = new JButton();
- button.setActionCommand(EDIT);
- button.addActionListener(this);
- button.setBorderPainted(false);
- //Set up the dialog that the button brings up.
- colorChooser = new JColorChooser();
- dialog = JColorChooser.createDialog(button,
- "Pick a Color",
- true, //modal
- colorChooser,
- this, //OK button handler
- null); //no CANCEL button handler
- }
- public void actionPerformed(ActionEvent e) {
- if (EDIT.equals(e.getActionCommand())) {
- //The user has clicked the cell, so
- //bring up the dialog.
- button.setBackground(currentColor);
- colorChooser.setColor(currentColor);
- dialog.setVisible(true);
- fireEditingStopped(); //Make the renderer reappear.
- } else { //User pressed dialog's "OK" button.
- currentColor = colorChooser.getColor();
- }
- }
- //Implement the one CellEditor method that AbstractCellEditor doesn't.
- public Object getCellEditorValue() {
- return currentColor;
- }
- //Implement the one method defined by TableCellEditor.
- public Component getTableCellEditorComponent(JTable table,
- Object value,
- boolean isSelected,
- int row,
- int column) {
- currentColor = (Color)value;
- return button;
- }
- }
这段代码很简单。比较难懂的是 fireEditingStopped。如果没有这句,editor 保持激活,即便对话框不再可视。它的调用让 table 知道 editor 已经可以无效,让 renderer 来处理单元格。
(15)使用编辑器验证用户的文本输入
如果一个单元格默认的 editor 允许空文本, 当文本不是你指定的某些东西,你想获得错误提示。错误检查伴随发生在输入的文本转换为合适类型的对象的时候。
当默认editor视图创建一个关联单元格列的 class 实例时,这种对用户输入的字符串自动检测就会发生。这个默认的 editor 使用字符串为参数的够着函数创建该实例。例如,在单元格的数据类型为Integer的列中,当用户敲入“123”,默认的 editor 创建对应的Integer类型,使用等同于 new Integer(“123”)。如果构造器抛出一个异常,单元格的outline变成红,该单元格不允许失去聚焦。如果你的列数据类型对应的 class 可以使用字符串参数构造实例,则可以是哦那个默认的 editor 完成检测。
如果你喜欢用 文本域 作为单元格的 editor,又想自定义某些检测方式,或是定义发现错误时的不同表现。你可以使用 formatted text field. ,formatted text field 可以检测在输入期间或者输入完成时检测是否错误。
例子 TableFTFEditDemo.java
,,建立一个 formatted text field 作为 editor。限制所有整数值只能为 0~100之间:
下面这句代码使 formatted text field 成为所有包含 Integer 类型数据的列的 editor。IntegerEditor.java
查看
- table.setDefaultEditor(Integer.class,
- new IntegerEditor(0, 100));
IntegerEditor 是 DefaultCellEditor
的子类。使用 DefaultCellEditor 支持的 JFormattedTextField 代替 JTextField。使用 integer format 建立一个 formatted text field,然后指定最大最小值。参考 How to Use Formatted Text Fields. 接着覆盖 DefaultCellEditor 要求的 getCellEditorValue 和 stopCellEditing 方法
getTableCellEditorComponent 这个覆盖的方法,在 editor 现实之前,正确的设置 formatted text field 的值(不是简单的继承JTextField的值)。
getCellEditorValue
方法则保持 单元格的值为 一个Integer,而不是 formatted text field 试图返回的long或者之类的。最后 stopCellEditing 是的可以检测文本是否合法,可能防止了 editor 被解除。如果文本不合法,你的 stopCellEditing 给出对话框,让用户选择是继续编辑还是会退到最后输入的合法值。代码太长,请查看:
IntegerEditor.java
(16)打印
JTable 提供了一个简单的 API 用于打印表格。打印表格的最简单的方法就是直接调用无参数的 JTable.print
- try {
- if (! table.print()) {
- System.err.println("User cancelled printing");
- }
- } catch (java.awt.print.PrinterException e) {
- System.err.format("Cannot print %s%n", e.getMessage());
- }
在一个标准Swing应用程序中,调用 print 方法会弹出一个标准的打印对话框。返回值指示用户继续还是取消打印作业。 JTable.print 可能抛出 java.awt.print.PrinterException ,是check Exception,所以要trycatch
JTable 提供了多种 print 的重载。 来自 TablePrintDemo.java
的代码战士如何定义一个 page header:
- MessageFormat header = new MessageFormat("Page {0,number,integer}");
- try {
- table.print(JTable.PrintMode.FIT_WIDTH, header, null);
- } catch (java.awt.print.PrinterException e) {
- System.err.format("Cannot print %s%n", e.getMessage());
- }
有关更复杂的打印应用,使用 JTable.getPrintable
获得一个 Printable 对象。有关 Printable 的内容,参考refer to the Printing lesson in the 2D Graphics trail.
(17)例子列表
Example Where Described NotesSimpleTableDemo |
Creating a Simple Table | A basic table with no custom model. Does not include code tospecify column widths or detect user editing. |
SimpleTable- |
Detecting User Selections | Adds single selection and selection detection to SimpleTableDemo . By modifying the program's ALLOW_COLUMN_SELECTION andALLOW_ROW_SELECTION constants, you can experiment with alternatives to the table default of allowing only rows to be selected. |
TableDemo |
Creating a Table Model | A basic table with a custom model. |
TableFTFEditDemo |
Using an Editor to Validate User-Entered Text | Modifies TableDemo to use a custom editor (a formatted text field variant) for all Integer data. |
TableRenderDemo |
Using a Combo Box as an Editor | Modifies TableDemo to use a custom editor (a combo box) for all data in the Sport column. Also intelligently picks column sizes. Uses renderers to display tool tips for the sport cells. |
TableDialogEditDemo |
Using Other Editors | Modifies TableDemo to have a cell renderer and editor that display a color and let you choose a new one, using a color chooser dialog. |
TableToolTipsDemo |
Specifying Tool Tips for Cells,Specifying Tool Tips for Column Headers, | Demonstrates how to use several techniques to set tool tip text for cells and column headers. |
TableSortDemo |
Sorting and Filtering | Demonstrates the default sorter, which allows the user to sort columns by clicking on their headers. |
TableFilterDemo |
Sorting and Filtering | Demonstrates sorting and filtering, and how this can cause the view coordinates to diverge from the model coordinates. |
TablePrintDemo |
Printing | Demonstrates table printing. |
ListSelectionDemo |
How to Write a List Selection Listener | Shows how to use all list selection modes, using a list selection listener that's shared between a table and list. |
SharedModelDemo |
Nowhere | Builds on ListSelectionDemo making the data model be shared between the table and list. If you edit an item in the first column of the table, the new value is reflected in the list. |
TreeTable, TreeTable II | Creating TreeTables in Swing, Creating TreeTables: Part 2 | Examples that combine a tree and table to show detailed information about a hierarchy such as a file system. The tree is a renderer for the table. |