本教程是针对JDK 8编写的。本页面描述的示例和实践不利用后续版本中引入的改进,并且可能使用不再可用的技术。
有关Java SE 9及后续版本中更新的语言特性的摘要,请参见Java语言变更。
有关所有JDK版本的新功能、增强功能以及已删除或已弃用选项的信息,请参见JDK发行说明。
使用JTable
类可以显示数据表,还可以选择允许用户编辑数据。 JTable
不包含或缓存数据;它只是你的数据的一个视图。下面是一个在滚动窗格中显示的典型表的图片:
本节的其余部分将向您展示如何完成一些常见的与表格相关的任务。以下是本节涵盖的主题:
点击“Launch”按钮运行SimpleTableDemo
,使用Java™ Web Start(下载JDK 7或更高版本)。或者,要编译和运行示例,请参考示例索引。
点击包含“滑雪”的单元格。
整个第一行被选中,表示您选择了Kathy Smith的数据。特殊的高亮表示“滑雪”单元格可编辑。通常,双击文本单元格可以开始编辑。
将光标移到“First Name”上。现在按下鼠标按钮并向右拖动。
如您所见,用户可以重新排列表中的列。
将光标放在列标题的右边。现在按下鼠标按钮并向右或向左拖动。
列的大小会改变,其他列会自动调整以填充剩余空间。
在SimpleTableDemo.java
中的表格声明了一个字符串数组作为列名:
String[] columnNames = {"名字", "姓氏", "运动", "年限", "素食者"};
数据被初始化并存储在一个二维对象数组中:
Object[][] data = { {"凯西", "史密斯", "滑雪", new Integer(5), new Boolean(false)}, {"约翰", "杜", "划船", new Integer(3), new Boolean(true)}, {"苏", "布莱克", "织毛衣", new Integer(2), new Boolean(false)}, {"简", "怀特", "速读", new Integer(20), new Boolean(true)}, {"乔", "布朗", "台球", new Integer(10), new Boolean(false)} };
然后使用这些数据和列名构建表格:
JTable table = new JTable(data, columnNames);
有两个JTable
构造函数可以直接接受数据(SimpleTableDemo
使用第一个):
JTable(Object[][] rowData, Object[] columnNames)
JTable(Vector rowData, Vector columnNames)
这些构造函数的优点是它们使用起来很容易。然而,这些构造函数也有缺点:
Boolean
数据,表格可以以复选框的形式显示数据。但是,如果使用前面列出的任何一个JTable
构造函数,Boolean
数据将以字符串形式显示。你可以在前面的图中的Vegetarian
列中看到这个区别。如果你想避开这些限制,你需要自己实现一个表格模型,详见创建表格模型。
下面是创建滚动窗格作为表格容器的典型代码:
JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true);
这个代码片段中的两行代码分别执行以下操作:
JScrollPane
构造函数。这将创建一个滚动窗格作为表的容器;表会自动添加到容器中。JTable.setFillsViewportHeight
设置fillsViewportHeight
属性。当此属性为true
时,即使表没有足够的行来使用整个垂直空间,表也会使用容器的整个高度。这使得将表用作拖放目标更容易。滚动窗格会自动将表头放置在视口的顶部。当滚动表数据时,列名仍然保持在视区的顶部可见。
如果您使用的是没有滚动窗格的表,则必须获取表头组件并自行放置。例如:
container.setLayout(new BorderLayout()); container.add(table.getTableHeader(), BorderLayout.PAGE_START); container.add(table, BorderLayout.CENTER);
默认情况下,表中的所有列都具有相等的宽度,并且列自动填充整个表的宽度。当表变得更宽或更窄时(当用户调整包含表的窗口大小时可能会发生),所有列的宽度会相应地改变。
当用户通过拖动列的右边界调整列大小时,要么其他列必须改变大小,要么表的大小必须改变。默认情况下,表的大小保持不变,右边界拖动点左侧的列的宽度增加或减少的空间会被分配给右边的所有列。
要自定义初始列宽度,可以在表的每个列上调用setPreferredWidth
。这将设置列的首选宽度和其大致相对宽度。例如,将以下代码添加到SimpleTableDemo
将使其第三列比其他列更宽:
TableColumn column = null; for (int i = 0; i < 5; i++) { column = table.getColumnModel().getColumn(i); if (i == 2) { column.setPreferredWidth(100); //第三列更宽 } else { column.setPreferredWidth(50); } }
如上述代码所示,表中的每个列由一个TableColumn
对象表示。TableColumn
提供了用于列的最小、首选和最大宽度的getter和setter方法,以及用于获取当前宽度的方法。有关根据绘制单元格内容所需空间的近似值设置单元格宽度的示例,请参阅TableRenderDemo.java
中的initColumnSizes
方法。
当用户显式调整列大小时,列的首选宽度被设置为用户指定的大小,成为列的新当前宽度。然而,当表本身被调整大小时(通常是因为窗口被调整大小),列的首选宽度不会改变。相反,现有的首选宽度被用来计算新的列宽以填充可用空间。
您可以通过调用setAutoResizeMode
来改变表的调整大小行为。
在默认配置下,表支持由一个或多个行组成的选择。用户可以选择一系列连续的行或任意一组行。用户最后指示的单元格会有一个特殊的标记;在Metal外观中,该单元格被勾勒出来。这个单元格被称为主导选择;有时也称为“焦点单元格”或“当前单元格”。
用户使用鼠标和/或键盘进行选择,如下表所述:
操作 | 鼠标操作 | 键盘操作 |
---|---|---|
选择单行。 | 点击。 | 向上箭头或向下箭头。 |
扩展连续选择。 | Shift-点击或拖动行。 | Shift-向上箭头或Shift-向下箭头。 |
将行添加到选择/切换行选择。 | Control-点击 | 使用Control-向上箭头或Control-向下箭头移动主导选择,然后使用空格键添加到选择或使用Control-空格键切换行选择。 |
要查看选择的工作原理,请点击“启动”按钮以运行TableSelectionDemo
使用Java™ Web Start(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引。
此示例程序展示了熟悉的表,并允许用户操作某些JTable选项。还有一个文本窗格用于记录选择事件。
在下面的屏幕截图中,用户运行了该程序,点击了第一行,然后使用Ctrl键点击了第三行。请注意最后点击的单元格周围的轮廓;这是Metal外观如何突出显示主导选择。
在“选择模式”下有一组单选按钮。点击标有“单选”标签的按钮。现在你只能一次选择一行。如果点击“单区间选择”单选按钮,则可以选择一组必须连续的行。
“选择模式”下的所有单选按钮都调用了 JTable.setSelectionMode
方法。此方法接受一个参数,该参数必须是 javax.swing.ListSelectionModel
中定义的以下常量之一:MULTIPLE_INTERVAL_SELECTION
、SINGLE_INTERVAL_SELECTION
和 SINGLE_SELECTION
。
回到 TableSelectionDemo
,注意“选择选项”下的三个复选框。每个复选框控制着 JTable
定义的一个 boolean
类型的绑定变量的状态:
rowSelectionAllowed
,该变量具有 setter 方法 setRowSelectionAllowed
和 getter 方法 getRowSelectionAllowed
。当该绑定属性为 true
(且 columnSelectionAllowed
属性为 false
)时,用户可以按行选择。columnSelectionAllowed
,该变量具有 setter 方法 setColumnSelectionAllowed
和 getter 方法 getColumnSelectionAllowed
。当该绑定属性为 true
(且 rowSelectionAllowed
绑定属性为 false
)时,用户可以按列选择。cellSelectionEnabled
,该变量具有 setter 方法 setCellSelectionEnabled
和 getter 方法 getCellSelectionEnabled
。当该绑定属性为 true
时,用户可以选择单个单元格或矩形区域的单元格。JTable
使用一个非常简单的选择概念,作为行和列的交集进行管理。它不支持完全独立的单元格选择。
如果清除所有三个复选框(将所有三个绑定属性设置为 false
),则没有选择;只有主选择会显示。
你可能会注意到,在多区间选择模式下,“单元格选择”复选框是被禁用的。这是因为在演示中不支持此模式下的单元格选择。你可以在多区间选择模式下指定单元格选择,但结果是一个无法产生有用选择的表格。
你可能还会注意到,更改任何三个选择选项可能会影响其他选项。这是因为允许行选择和列选择的同时,也等同于启用了单元格选择。JTable
会自动根据需要更新这三个绑定变量,以保持它们的一致性。
cellSelectionEnabled
的值会同时将rowSelectionEnabled
和columnSelectionEnabled
的值设置为相同的值。同时设置rowSelectionEnabled
和columnSelectionEnabled
的值会同时将cellSelectionEnabled
的值设置为相同的值。将rowSelectionEnabled
和columnSelectionEnabled
的值设置为不同的值会同时将cellSelectionEnabled
的值设置为false
。
要获取当前选择的内容,使用JTable.getSelectedRows
方法获取行索引数组,使用JTable.getSelectedColumns
方法获取列索引数组。要获取主要选择的坐标,可以参考表格本身和表格的列模型的选择模型。以下代码格式化一个包含主要选择行和列的字符串:
String.format("主要选择:%d,%d。", table.getSelectionModel().getLeadSelectionIndex(), table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
用户选择会触发多个事件。有关这些事件的信息,请参考如何编写列表选择监听器课程中的编写事件监听器。
每个表格对象都使用一个表格模型对象来管理实际的表格数据。表格模型对象必须实现TableModel
接口。如果程序员没有提供表格模型对象,JTable
会自动创建一个DefaultTableModel
的实例。下面是这种关系的示例:
SimpleTableDemo
使用的JTable
构造函数使用以下代码创建其表格模型:
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); } }
正如上述代码所示,实现表格模型可以很简单。通常情况下,您会在AbstractTableModel
类的子类中实现您的表格模型。
您的模型可以将数据存储在数组、向量或哈希映射中,或者它可以从外部源(如数据库)获取数据。它甚至可以在执行时生成数据。
此表格与SimpleTableDemo
表格有以下不同之处:
TableDemo
的自定义表格模型虽然简单,但可以轻松确定数据的类型,从而帮助JTable
以最佳格式显示数据。另一方面,SimpleTableDemo
的自动生成的表格模型不知道# of Years列包含数字(通常应该右对齐并具有特定格式)。它还不知道Vegetarian
列包含布尔值,可以用复选框表示。TableDemo
中实现的自定义表格模型不允许编辑名称列;但是,它允许您编辑其他列。在SimpleTableDemo
中,所有单元格都是可编辑的。下面是从TableDemo.java
中提取的代码,与SimpleTableDemo.java
不同。粗体字表示此表格的模型与为SimpleTableDemo
自动定义的表格模型不同的代码。
public TableDemo() { ... JTable table = new JTable(new MyTableModel()); ... } class MyTableModel extends AbstractTableModel { private String[] columnNames = ...//与之前相同... private Object[][] data = ...//与之前相同... 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]; } public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } /* * 除非表格可编辑,否则不需要实现此方法。 */ public boolean isCellEditable(int row, int col) { //注意,数据/单元格地址是恒定的, //无论单元格在屏幕上的位置如何。 if (col < 2) { return false; } else { return true; } } /* * 除非表格数据可以更改,否则不需要实现此方法。 */ public void setValueAt(Object value, int row, int col) { data[row][col] = value; fireTableCellUpdated(row, col); } ... }
表格模型可以有一组监听器,每当表格数据发生变化时,它们都会收到通知。监听器是 TableModelListener
的实例。在下面的示例代码中,SimpleTableDemo
被扩展以包含这样的监听器。新代码用粗体表示。
import javax.swing.event.*; import javax.swing.table.TableModel; public class SimpleTableDemo ... implements TableModelListener { ... public SimpleTableDemo() { ... table.getModel().addTableModelListener(this); ... } 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); ...// 对数据进行操作... } ... }
为了触发数据变化事件,表格模型必须知道如何构造一个 TableModelEvent
对象。这可能是一个复杂的过程,但已经在 DefaultTableModel
中实现了。您可以允许 JTable
使用其默认的 DefaultTableModel
实例,或者创建自己的自定义 DefaultTableModel
的子类。
如果DefaultTableModel
不适合作为自定义表格模型类的基类,请考虑子类化AbstractTableModel
。该类实现了一个简单的框架来构造TableModelEvent
对象。您的自定义类只需在每次表格数据由外部源更改时调用以下AbstractTableModel
方法之一。
方法 | 变更 |
---|---|
fireTableCellUpdated |
更新指定单元格。 |
fireTableRowsUpdated |
更新指定行。 |
fireTableDataChanged |
更新整个表格(仅数据)。 |
fireTableRowsInserted |
插入新行。 |
fireTableRowsDeleted |
删除现有行。 |
fireTableStructureChanged |
使整个表格无效,包括数据和结构。 |
在继续下面的任务之前,您需要了解表格如何绘制其单元格。您可能期望表格中的每个单元格都是一个组件。然而,出于性能原因,Swing表格实现方式不同。
相反,通常使用一个单一的单元格渲染器来绘制包含相同类型数据的所有单元格。您可以将渲染器视为一个可配置的印章,表格使用它将格式正确的数据印到每个单元格上。当用户开始编辑单元格的数据时,单元格编辑器接管单元格,控制单元格的编辑行为。
例如,TableDemo
中年数列中的每个单元格都包含Number
数据 - 具体而言,是一个Integer
对象。默认情况下,包含Number
的列的单元格渲染器使用一个右对齐的JLabel
实例在列的单元格上绘制适当的数字。如果用户开始编辑其中一个单元格,则默认的单元格编辑器使用一个右对齐的JTextField
来控制单元格的编辑。
为了选择在列中显示单元格的渲染器,表格首先确定您是否为该特定列指定了渲染器。如果没有指定,表格会调用表格模型的getColumnClass
方法,该方法获取列的单元格的数据类型。接下来,表格将列的数据类型与已注册的单元格渲染器的数据类型列表进行比较。此列表由表格进行初始化,但您可以添加或更改它。目前,表格将以下类型的数据放入列表中:
Boolean
— 用复选框呈现。Number
— 用右对齐的标签呈现。Double
、Float
— 与Number
相同,但对象到文本的转换由一个NumberFormat
实例执行(使用当前区域设置的默认数字格式)。Date
— 由标签呈现,对象到文本的转换由一个DateFormat
实例执行(使用日期和时间的短格式)。ImageIcon
、Icon
— 由居中的标签呈现。Object
— 由显示对象字符串值的标签呈现。使用类似的算法选择单元格编辑器。
请记住,如果您让表格创建自己的模型,它将使用Object
作为每一列的类型。要指定更精确的列类型,表格模型必须适当地定义getColumnClass
方法,就像TableDemo.java
中所演示的那样。
请记住,尽管渲染器决定每个单元格或列标题的外观并可以指定其工具提示文本,但渲染器不处理事件。如果您需要捕获发生在表格内的事件,您所使用的技术取决于您感兴趣的事件类型:
情况 | 如何获取事件 |
---|---|
要检测正在编辑的单元格的事件... | 使用单元格编辑器(或在单元格编辑器上注册侦听器)。 |
要检测行/列/单元格的选择和取消选择的事件... | 使用选择侦听器,如检测用户选择中所述。 |
要检测列标题上的鼠标事件... | 在表格的JTableHeader 对象上注册适当类型的鼠标侦听器。(有关示例,请参见TableSorter.java 。) |
要检测其他事件... | 在JTable 对象上注册适当的侦听器。 |
接下来的几个部分将告诉您如何通过指定渲染器和编辑器来自定义显示和编辑。您可以按列或按数据类型指定单元格渲染器和编辑器。
本节将告诉您如何创建和指定单元格渲染器。您可以使用JTable
方法setDefaultRenderer
设置特定类型的单元格渲染器。要指定特定列中的单元格使用渲染器,可以使用TableColumn
方法setCellRenderer
。您甚至可以通过创建JTable
子类来指定特定单元格的渲染器。
很容易自定义默认渲染器DefaultTableCellRenderer
的文本或图像。只需创建一个子类,并实现setValue
方法,使其调用适当的字符串或图像的setText
或setIcon
。例如,下面是默认日期渲染器的实现:
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
不足够,可以使用另一个超类构建渲染器。最简单的方法是创建一个现有组件的子类,使您的子类实现TableCellRenderer
接口。 TableCellRenderer
只需要一个方法:getTableCellRendererComponent
。您实现此方法应该设置渲染组件以反映传入的状态,然后返回该组件。
在TableDialogEditDemo
的快照中,用于Favorite Color单元格的渲染器是JLabel
的子类ColorRenderer
。以下是ColorRenderer.java
的摘录,显示了其实现方式。
public class ColorRenderer extends JLabel implements TableCellRenderer { ... public ColorRenderer(boolean isBordered) { this.isBordered = isBordered; setOpaque(true); //必须这样设置才能显示背景。 } 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是颜色表的solid border。 setBorder(selectedBorder); } else { ... //unselectedBorder是颜色表的solid border。 setBorder(unselectedBorder); } } setToolTipText(...); //在下一节中讨论 return this; } }
这是来自TableDialogEditDemo.java
的代码,它将ColorRenderer
实例注册为所有Color
数据的默认渲染器:
table.setDefaultRenderer(Color.class, new ColorRenderer(true));
要指定单元格特定的渲染器,您需要定义一个覆盖getCellRenderer
方法的JTable
子类。例如,以下代码使表格的第一列的第一个单元格使用自定义渲染器:
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); } };
默认情况下,表格单元格的工具提示文本由单元格的渲染器确定。然而,有时候通过覆盖JTable
的getToolTipText(MouseEvent)
方法来指定工具提示文本会更简单。本节将向您展示如何同时使用这两种技术。
要使用渲染器为单元格添加工具提示,您首先需要获取或创建单元格渲染器。然后,在确保渲染组件是JComponent
的情况下,调用其上的setToolTipText
方法。
在TableRenderDemo
中有设置单元格工具提示的示例。点击“启动”按钮以使用Java™ Web Start运行它(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引。
源代码位于TableRenderDemo.java
。它使用以下代码为Sport列的单元格添加工具提示:
//为运动单元格设置工具提示。 DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); renderer.setToolTipText("单击以显示下拉框"); sportColumn.setCellRenderer(renderer);
尽管前面示例中的工具提示文本是静态的,但您也可以实现根据单元格或程序状态而变化的工具提示。以下是几种实现方式:
getTableCellRendererComponent
方法的实现中添加一些代码。JTable
的getToolTipText(MouseEvent)
方法。在TableDialogEditDemo
中添加代码到单元格渲染器的示例。单击“启动”按钮使用Java™ Web Start运行它(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参阅示例索引。
TableDialogEditDemo
使用在ColorRenderer.java中实现的颜色渲染器,使用下面代码中的粗体代码设置工具提示文本:
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值:" + newColor.getRed() + "," + newColor.getGreen() + "," + newColor.getBlue()); return this; } }
下面是工具提示的示例:
您可以通过重写JTable
的getToolTipText(MouseEvent)
方法来指定工具提示文本。程序TableToolTipsDemo
展示了如何实现。单击“启动”按钮使用Java™ Web Start运行它(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参阅示例索引。
具有工具提示的单元格位于Sport和Vegetarian列中。以下是工具提示的图片:
以下是实现Sport和Vegetarian列单元格工具提示的TableToolTipsDemo.java
代码:
JTable table = new JTable(new MyTableModel()) { //实现表格单元格工具提示。 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列 tip = "这个人最喜欢参与的运动是:" + getValueAt(rowIndex, colIndex); } else if (realColumnIndex == 4) { //Veggie列 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 + " 是素食者"; } else { tip = firstName + " " + lastName + " 不是素食者"; } } else { //其他列 //如果您知道没有提供自己的工具提示的渲染器,可以省略此部分。 tip = super.getToolTipText(e); } return tip; } ... }
代码非常简单,除了调用convertColumnIndexToModel
可能有些不太清楚。这个调用是必要的,因为如果用户移动了列,视图的索引与模型的索引将不匹配。例如,用户可能会拖动Vegetarian列(模型认为它在索引4处),以便它显示为第一列-在视图索引0处。由于prepareRenderer
提供了视图索引,您需要将视图索引转换为模型索引,以确保选择了预期的列。
您可以通过设置表格的JTableHeader
的工具提示文本来为列标题添加工具提示。通常,不同的列标题需要不同的工具提示文本。您可以通过重写表头的getToolTipText
方法来更改文本。或者,您可以调用TableColumn.setHeaderRenderer
为表头提供自定义渲染器。
在TableSorterDemo.java
中有一个使用相同工具提示文本的示例。以下是它如何设置工具提示文本:
table.getTableHeader().setToolTipText( "单击以排序;Shift-单击以按相反顺序排序");
TableToolTipsDemo.java
演示了按列实现变化的列标题工具提示的示例。如果使用Java™ Web Start(下载JDK 7或更高版本)运行TableToolTipsDemo
(单击启动按钮)。或者,要编译和运行示例,请参阅示例索引。
当您将鼠标悬停在除前两个之外的任何列标题上时,您将看到工具提示。由于名称列似乎不言自明,因此没有提供工具提示。以下是列标题工具提示的示例图片:
以下代码实现了工具提示。基本上,它创建了JTableHeader
的子类,重写了getToolTipText(MouseEvent)
方法,以返回当前列的文本。为了将修改后的表头与表格关联起来,重写了JTable
方法createDefaultTableHeader
,使其返回JTableHeader
子类的实例。
protected String[] columnToolTips = { null, // "姓",显而易见 null, // "名",显而易见 "个人参与的最喜欢的运动", "个人从事该运动的年数", "如果选中,表示个人不吃肉"}; ... JTable table = new JTable(new MyTableModel()) { ... // 实现表头工具提示 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]; } }; } };
表格的排序和筛选由一个排序器对象管理。最简单的方法是将autoCreateRowSorter
绑定属性设置为true
:
JTable table = new JTable(); table.setAutoCreateRowSorter(true);
这个操作会定义一个排序器对象,它是javax.swing.table.TableRowSorter
的实例。当用户点击表格列头时,它会进行一个简单的区域特定排序。这在
中有示例,如下图所示:TableSortDemo.java
如果想要更多对排序的控制,可以构造一个TableRowSorter
的实例,并将其设置为表格的排序器对象。
TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel()); table.setRowSorter(sorter);
TableRowSorter
使用java.util.Comparator
对象来对行进行排序。实现了这个接口的类必须提供一个名为compare
的方法,用于定义两个对象在排序时如何进行比较。例如,下面的代码创建了一个按每个字符串中的最后一个单词排序的Comparator
:
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
。
为了确定在列上使用哪个 Comparator
,TableRowSorter
依次尝试应用以下规则。规则按照下面列出的顺序进行;第一个为排序器提供 Comparator
的规则将被使用,而其他规则将被忽略。
setComparator
指定了比较器,使用该比较器。TableModel.getColumnClass
返回 String.class
),使用根据当前区域设置对字符串进行排序的比较器。TableModel.getColumnClass
返回的列类实现了 Comparable
,使用根据 Comparable.compareTo
返回值对字符串进行排序的比较器。setStringConverter
为表指定了字符串转换器,使用根据当前区域设置对结果字符串表示进行排序的比较器。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);
除了重新排序结果,表格排序器还可以指定显示哪些行,这被称为过滤。 TableRowSorter
使用 javax.swing.RowFilter
对象来实现过滤。 RowFilter
实现了几个工厂方法,用于创建常见类型的过滤器。例如,regexFilter
返回一个基于正则表达式的 RowFilter
。
在下面的示例代码中,您明确创建了一个排序器对象,以便以后可以使用它来指定过滤器:
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); }
在后续示例中,每次文本字段更改时都会调用newFilter()
。当用户输入复杂的正则表达式时,try...catch
会阻止语法异常干扰输入。
当表格使用排序器时,用户看到的数据可能与数据模型指定的顺序不同,并且可能不包括数据模型指定的所有行。用户实际看到的数据称为视图,并具有其自己的坐标系。 JTable
提供了一些方法,用于将模型坐标转换为视图坐标 - convertColumnIndexToView
和 convertRowIndexToView
,以及将视图坐标转换为模型坐标 - convertColumnIndexToModel
和 convertRowIndexToModel
。
下面的示例将本节讨论的思想汇集在一起。
对 TableFilterDemo.java
TableDemo
进行了少量的更改。其中包括本节前面提到的代码片段,为主表提供了一个排序器,并使用文本字段提供过滤正则表达式。下面的屏幕截图显示了在进行任何排序或过滤之前的 TableFilterDemo
。请注意,模型中的第3行仍然与视图中的第3行相同:
如果用户在第二列上点击两次,第四行将成为第一行——但仅在视图中如此:
正如前面所提到的,用户在“Filter Text”文本字段中输入的文本定义了确定显示哪些行的过滤器。与排序一样,过滤也会导致视图坐标与模型坐标不一致:
以下是更新状态字段以反映当前选择的代码:
table.getSelectionModel().addListSelectionListener( new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { int viewRow = table.getSelectedRow(); if (viewRow < 0) { //选择被过滤掉了。 statusText.setText(""); } else { int modelRow = table.convertRowIndexToModel(viewRow); statusText.setText( String.format("视图中的选定行:%d。模型中的选定行:%d。", viewRow, modelRow)); } } } );
将下拉框设置为编辑器很简单,如下面的示例所示。粗体代码行设置了下拉框作为特定列的编辑器。
TableColumn sportColumn = table.getColumnModel().getColumn(2); ... JComboBox comboBox = new JComboBox(); comboBox.addItem("滑雪"); comboBox.addItem("划船"); comboBox.addItem("追逐幼儿"); comboBox.addItem("速读"); comboBox.addItem("高中教学"); comboBox.addItem("无"); sportColumn.setCellEditor(new DefaultCellEditor(comboBox));
这里是使用组合框编辑器的示例图片:
上述代码来自TableRenderDemo.java
。您可以运行TableRenderDemo
(点击“Launch”按钮)使用Java™ Web Start(下载 JDK 7 或更高版本)。或者,如果您想要自己编译和运行示例,请参考示例索引。
无论是为单个单元格列设置编辑器(使用TableColumn
的setCellEditor
方法),还是为特定类型的数据设置编辑器(使用JTable
的setDefaultEditor
方法),您都需要使用符合TableCellEditor
接口的参数来指定编辑器。幸运的是,DefaultCellEditor
类实现了这个接口,并提供了构造函数,让您可以指定一个作为编辑组件的JTextField
、JCheckBox
或JComboBox
。通常情况下,您不需要明确指定复选框作为编辑器,因为具有Boolean
数据的列会自动使用复选框渲染器和编辑器。
如果您想要指定除文本字段、复选框或组合框以外的编辑器怎么办?由于DefaultCellEditor
不支持其他类型的组件,您需要做更多的工作。您需要创建一个实现TableCellEditor
接口的类。可以使用AbstractCellEditor
类作为一个很好的超类,它实现了TableCellEditor
的超接口CellEditor
,省去了为单元格编辑器实现必要的事件触发代码的麻烦。
您的单元格编辑器类需要至少定义两个方法——getCellEditorValue
和getTableCellEditorComponent
。由CellEditor
要求的getCellEditorValue
方法返回单元格的当前值。由TableCellEditor
要求的getTableCellEditorComponent
方法应该配置并返回您想要用作编辑器的组件。
这是一个带有对话框的表格图片,间接地作为单元格编辑器。当用户开始编辑“最喜欢的颜色”列中的单元格时,会出现一个按钮(真正的单元格编辑器),并弹出对话框,用户可以在对话框中选择不同的颜色。
您可以运行TableDialogEditDemo
(点击“Launch”按钮)使用Java™ Web Start(下载JDK 7或更高版本)。或者,要自己编译和运行示例,请参考示例索引。
这是从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); //设置弹出的对话框。 colorChooser = new JColorChooser(); dialog = JColorChooser.createDialog(button, "选择颜色", true, //模态 colorChooser, this, //OK按钮处理程序 null); //没有CANCEL按钮处理程序 } public void actionPerformed(ActionEvent e) { if (EDIT.equals(e.getActionCommand())) { //用户点击了单元格,因此 //弹出对话框。 button.setBackground(currentColor); colorChooser.setColor(currentColor); dialog.setVisible(true); fireEditingStopped(); //使渲染器重新出现。 } else { //用户按下对话框的“确定”按钮。 currentColor = colorChooser.getColor(); } } //实现AbstractCellEditor没有的一个CellEditor方法。 public Object getCellEditorValue() { return currentColor; } //实现TableCellEditor定义的一个方法。 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { currentColor = (Color)value; return button; } }
如你所见,代码非常简单。唯一有点棘手的部分是在编辑器按钮的操作处理程序的末尾调用fireEditingStopped
。如果没有这个调用,即使模态对话框不再可见,编辑器仍然保持活动状态。调用fireEditingStopped
让表格知道它可以停用编辑器,让单元格再次由渲染器处理。
如果一个单元格的默认编辑器允许文本输入,如果单元格的类型指定为除String
或Object
之外的其他类型,您将免费获得一些错误检查。错误检查是将输入的文本转换为正确类型的对象的副作用。
自动检查用户输入的字符串发生在默认编辑器尝试创建与单元格列关联的类的新实例时。默认编辑器使用以String
为参数的构造函数创建此实例。例如,在单元格类型为Integer
的列中,当用户输入"123"时,默认编辑器使用等效于new Integer("123")
的代码创建相应的Integer
。如果构造函数抛出异常,单元格的轮廓变为红色,并且拒绝让焦点移出单元格。如果您实现了一个用作列数据类型的类,如果您的类提供了一个以String
类型的单个参数作为参数的构造函数,您可以使用默认编辑器。
如果您喜欢将文本字段作为单元格的编辑器,但希望自定义它 - 例如更严格地检查用户输入的文本或在文本无效时以不同方式进行反应 - 您可以更改单元格编辑器以使用格式化文本字段。格式化文本字段可以在用户输入时连续检查值,或在用户指示输入结束后(例如按下Enter键)进行检查。
下面的代码摘自一个名为TableFTFEditDemo.java
的演示,它设置了一个格式化文本字段作为编辑器,将所有整数值限制在0和100之间。您可以运行TableFTFEditDemo
(点击Launch按钮),使用Java™ Web Start(下载JDK 7或更高版本)。或者,要编译和运行示例,请参阅示例索引。
以下代码将格式化文本字段设置为包含Integer
类型数据的所有列的编辑器。
table.setDefaultEditor(Integer.class, new IntegerEditor(0, 100));
IntegerEditor
类是DefaultCellEditor
的子类,它使用JFormattedTextField
而不是DefaultCellEditor
支持的JTextField
。它通过首先设置格式化文本字段以使用整数格式并具有指定的最小和最大值来实现,使用如何使用格式化文本字段中描述的API。然后,它重写了DefaultCellEditor
的getTableCellEditorComponent
、getCellEditorValue
和stopCellEditing
方法,添加了格式化文本字段所需的操作。
getTableCellEditorComponent
的重写在编辑器显示之前设置格式化文本字段的value属性(而不仅仅是它从JTextField
继承的text属性)。getCellEditorValue
的重写将单元格值保持为Integer
,而不是格式化文本字段的解析器往往返回的Long
值。最后,重写stopCellEditing
允许您检查文本是否有效,可能阻止编辑器被关闭。如果文本无效,stopCellEditing
的实现会弹出一个对话框,让用户选择继续编辑或恢复到上一个好的值。源代码有点太长了,无法在此处包含,但您可以在IntegerEditor.java
中找到它。
JTable
为打印表格提供了简单的API。打印表格的最简单方法是调用没有参数的JTable.print
:
try { if (! table.print()) { System.err.println("用户取消打印"); } } catch (java.awt.print.PrinterException e) { System.err.format("无法打印 %s%n", e.getMessage()); }
在普通的Swing应用程序上调用print
会弹出一个标准的打印对话框。(在无头应用程序上,表格将被简单打印。)返回值表示用户是否继续进行打印作业或取消了。 JTable.print
可能会抛出java.awt.print.PrinterException
,这是一个已检查的异常;这就是为什么上面的示例使用了try ... catch
的原因。
JTable
提供了几个带有不同选项的print
方法重载。下面的代码来自
,演示了如何定义页面头部:TablePrintDemo.java
MessageFormat header = new MessageFormat("第 {0,number,integer} 页"); try { table.print(JTable.PrintMode.FIT_WIDTH, header, null); } catch (java.awt.print.PrinterException e) { System.err.format("无法打印 %s%n", e.getMessage()); }
对于更复杂的打印应用程序,使用JTable.getPrintable
来获取一个用于表格的Printable
对象。有关Printable
的更多信息,请参阅打印课程,位于2D 图形教程中。
此表列出了使用JTable
的示例以及这些示例的描述。
SimpleTableDemo
创建简单表格SimpleTable-
SelectionDemo
检测用户选择SimpleTableDemo
ALLOW_COLUMN_SELECTION
ALLOW_ROW_SELECTION
TableDemo
创建表格模型TableFTFEditDemo
使用编辑器验证用户输入的文本TableDemo
Integer
TableRenderDemo
使用下拉框作为编辑器TableDemo
SportTableDialogEditDemo
使用其他编辑器TableDemo
TableToolTipsDemo
为单元格指定工具提示为列标题指定工具提示TableSortDemo
排序和筛选TableFilterDemo
排序和过滤TablePrintDemo
打印ListSelectionDemo
如何编写列表选择监听器SharedModelDemo
ListSelectionDemo