如何使用表格
使用JTable类,您可以显示数据表,可以选择允许用户编辑数据。 JTable
不包含或缓存数据;它只是您的数据视图。这是滚动窗格中显示的典型表的图片:
本节的其余部分将向您展示如何完成一些与表相关的常见任务。以下是本节涵盖的主题:
创建简单表
Try this:
-
单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行
SimpleTableDemo
。或者,要自己编译并运行示例,请参考example index。 -
单击包含“单板滑雪”的单元格。
整个第一行都被选中,表明您已经选择了 Kathy Smith 的数据。特殊高亮显示“单板滑雪”单元格是可编辑的。通常,您可以通过 Double 击来开始编辑文本单元格。 -
将光标放在“名字”上。现在按下鼠标按钮并向右拖动。
如您所见,用户可以重新排列表中的列。 -
将光标定位在列标题的右侧。现在按下鼠标按钮并向右或向左拖动。
该列将更改大小,而其他列将进行调整以填充剩余空间。 -
调整包含表格的窗口的大小,使其大于显示整个表格所需的窗口。
所有表格单元都变得更宽,并扩展以填充额外的水平空间。
SimpleTableDemo.java中的表在 String 数组中声明列名:
String[] columnNames = {"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"};
其数据已初始化并存储在二维 Object 数组中:
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)}
};
然后,使用以下数据和 columnNames 构造表:
JTable table = new JTable(data, columnNames);
有两个JTable
构造函数直接接受数据(SimpleTableDemo
使用第一个):
-
JTable(Object[][] rowData, Object[] columnNames)
-
JTable(Vector rowData, Vector columnNames)
这些构造函数的优点是易于使用。但是,这些构造函数也有缺点:
-
它们自动使每个单元格可编辑。
-
它们将所有数据类型视为相同(作为字符串)。例如,如果表列包含
Boolean
数据,则该表可以在复选框中显示数据。但是,如果使用前面列出的两个JTable
构造函数之一,则Boolean
数据将显示为字符串。您可以在上图的Vegetarian
列中看到这种差异。 -
他们要求您将表的所有数据放入数组或向量中,这可能不适用于某些数据。例如,如果要从数据库实例化一组对象,则可能需要直接查询对象的值,而不是将所有值复制到数组或向量中。
如果要解决这些限制,则需要实现自己的表模型,如创建表模型中所述。
将表添加到容器
以下是用于创建用作表容器的scroll pane的典型代码:
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); //third column is bigger
} else {
column.setPreferredWidth(50);
}
}
如前面的代码所示,表中的每一列都由一个TableColumn对象表示。 TableColumn
提供了用于列的最小,首选和最大宽度的 getter 和 setter 方法,以及用于获取当前宽度的方法。有关根据绘制单元格内容所需的空间的近似值设置单元格宽度的示例,请参见TableRenderDemo.java中的initColumnSizes
方法。
当用户显式调整列的大小时,将设置列的“首选”宽度,以使用户指定的大小成为列的新的“当前”宽度。但是,当调整表本身的大小时(通常是因为窗口已调整大小);列的首选宽度不变。而是使用现有的首选宽度来计算新的列宽度以填充可用空间。
您可以通过调用setAutoResizeMode来更改表的调整大小行为。
User Selections
在其默认配置中,表支持由一个或多个行组成的选择。用户可以选择连续的行范围或任意行集。用户指示的最后一个单元格有特殊指示。在“金属”外观中,该单元格被概述。该单元格称为* lead selection *;有时称为“具有焦点的单元格”或“当前单元格”。
用户使用鼠标和/或键盘进行选择,如下表所示:
Operation | Mouse Action | Keyboard Action |
---|---|---|
选择单行。 | Click. | 向上箭头或向下箭头。 |
扩展连续选择。 | 按住 Shift 单击或拖动行。 | 上移箭头或下移箭头。 |
将行添加到选择/切换行选择。 | Control-Click | 使用 Control-上箭头或 Control-下箭头移动线索选择,然后使用空格键添加到选择中或使用 Control-空格键切换行选择。 |
要查看选择如何工作,请单击“启动”按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableSelectionDemo
。或者,要自己编译并运行示例,请参考example index。
该示例程序显示了熟悉的表,并允许用户操纵某些 JTable 选项。还有一个文本窗格,记录选择事件。
在下面的屏幕快照中,用户已运行该程序,在第一行中单击,然后在第三行中按住 Control 键单击。注意最后单击的单元格周围的轮廓;这就是 Metal 外观如何突出选择线索的方式。
在“选择 Pattern”下,有一组单选按钮。单击标记为“单一选择”的一个。现在您一次只能选择一行。如果单击“单个间隔选择”单选按钮,则可以选择一组必须连续的行。
“选择 Pattern”下的所有单选按钮都调用JTable.setSelectionMode。此方法采用单个参数,该参数必须是在javax.swing.ListSelectionModel
中定义的以下常量之一:MULTIPLE_INTERVAL_SELECTION
,SINGLE_INTERVAL_SELECTION
和SINGLE_SELECTION
。
返回TableSelectionDemo
,请注意“选择选项”下的三个选项复选框。每个复选框控制JTable
定义的boolean
绑定变量的状态:
-
“行选择”控件
rowSelectionAllowed
具有设置方法setRowSelectionAllowed
和获取方法getRowSelectionAllowed
。当此绑定属性为true
(并且columnSelectionAllowed
属性为false
)时,用户可以按行选择。 -
“列选择”控件
columnSelectionAllowed
具有设置方法setColumnSelectionAllowed
和获取方法getColumnSelectionAllowed
。当此绑定属性为true
(并且rowSelectionAllowed
绑定属性为false
)时,用户可以按列选择。 -
“单元格选择”控件
cellSelectionEnabled
具有设置方法setCellSelectionEnabled
和获取方法getCellSelectionEnabled
。当此绑定属性为true
时,用户可以选择单个单元格或矩形单元格块。
NOTE:
JTable
使用非常简单的选择概念,作为行和列的交集进行 管理。它并非设计用于处理完全独立的单元格选择。
如果清除所有三个复选框(将所有三个绑定属性都设置为false
),则没有选择;仅显示潜在 Client 选择。
您可能会注意到,在多个时间间隔选择 Pattern 下,“单元格选择”复选框被禁用。这是因为演示中的此 Pattern 不支持单元格选择。您可以在多个时间间隔选择 Pattern 下按单元格指定选择,但是结果是一个不会产生有用选择的表。
您可能还会注意到,更改三个选择选项中的任何一个都会影响其他选择。这是因为同时允许行选择和列选择与启用单元格选择完全相同。 JTable
根据需要自动更新三个绑定变量,以使其保持一致。
NOTE:
将cellSelectionEnabled
设置为一个值具有将rowSelectionEnabled
和columnSelectionEnabled
都设置为该值的副作用。将rowSelectionEnabled
和columnSelectionEnabled
都设置为一个值也具有将cellSelectionEnabled
设置为该值的副作用。将rowSelectionEnabled
和columnSelectionEnabled
设置为不同的值也会产生将cellSelectionEnabled
设置为false
的副作用。
要检索当前选择,请使用JTable.getSelectedRows返回行索引数组,并使用JTable.getSelectedColumns返回列索引数组。要检索潜在顾客选择的坐标,请参考表本身和表的列模型的选择模型。以下代码格式化包含潜在顾客选择的行和列的字符串:
String.format("Lead Selection: %d, %d. ",
table.getSelectionModel().getLeadSelectionIndex(),
table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
用户选择会产生许多事件。有关这些信息,请参阅编写事件监听器类中的如何编写列表选择监听器。
NOTE:
选择数据实际上描述了“视图”(在任何排序或过滤后显示的表数据)而不是表模型中的选定单元格。除非通过排序,过滤或用户对列的操作重新排列了查看的数据,否则区别并不重要。在这种情况下,必须使用排序和过滤中描述的转换方法转换选择坐标。
创建表模型
每个表对象都使用一个表模型对象来 管理 实际的表数据。表模型对象必须实现TableModelinterface。如果程序员不提供表模型对象,则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
的自动创建的表模型不知道 年数 列包含数字(通常应右对齐并具有特定格式)。还不知道Vegetarian
列包含布尔值,可以用复选框表示。 -
在
TableDemo
中实现的自定义表模型不允许您编辑名称列。但是,它允许您编辑其他列。在SimpleTableDemo
中,所有单元格都是可编辑的。
参见下文,从TableDemo.java提取的代码与SimpleTableDemo.java不同。粗体字体表示使此表的模型不同于为SimpleTableDemo
自动定义的表模型的代码。
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];
}
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
/*
* 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.
if (col < 2) {
return false;
} else {
return true;
}
}
/*
* 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);
}
...
}
监听数据更改
表模型可以具有一组侦听器,每当表数据发生更改时,这些侦听器都会收到通知。侦听器是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);
...// Do something with the data...
}
...
}
触发数据更改事件
为了触发数据更改事件,表模型必须知道如何构造TableModelEvent对象。这可能是一个复杂的过程,但已在DefaultTableModel
中实现。您可以允许JTable
使用其默认实例DefaultTableModel
,也可以创建自己的自定义子类DefaultTableModel
。
如果DefaultTableModel
不是自定义表模型类的合适 Base Class,请考虑子类AbstractTableModel。此类实现了一个用于构造TableModelEvent
对象的简单框架。每当外部数据源更改表数据时,您的自定义类仅需要调用以下AbstractTableModel
方法之一。
Method | Change |
---|---|
fireTableCellUpdated | 指定单元格的更新。 |
fireTableRowsUpdated | 指定行的更新 |
fireTableDataChanged | 整个表的更新(仅数据)。 |
fireTableRowsInserted | 插入了新行。 |
fireTableRowsDeleted | 现有行已删除 |
fireTableStructureChanged | 无效整个表,包括数据和结构。 |
概念:编辑器和渲染器
在 continue 下几个任务之前,您需要了解表如何绘制其单元格。您可能希望表中的每个单元格都是一个组件。但是,出于性能原因,Swing 表的实现方式有所不同。
相反,通常使用单个“单元格渲染器”绘制包含相同类型数据的所有单元格。您可以将渲染器视为可配置的墨水戳,表格使用该墨水戳将格式正确的数据标记到每个单元格上。当用户开始编辑单元格的数据时,“单元格编辑器”将接管该单元格,从而控制该单元格的编辑行为。
例如,TableDemo
的“年数”列中的每个单元格都包含Number
数据,具体来说就是Integer
对象。默认情况下,包含Number
的列的单元格渲染器使用单个JLabel
实例在列的单元格上绘制右对齐的适当数字。如果用户开始编辑单元格之一,则默认的单元格编辑器将使用右对齐的JTextField
来控制单元格编辑。
要选择在列中显示单元格的渲染器,表格首先确定您是否为该特定列指定了渲染器。如果没有,则表将调用表模型的getColumnClass
方法,该方法将获取列单元格的数据类型。接下来,该表将列的数据类型与为其注册了单元格渲染器的数据类型列表进行比较。该列表由表初始化,但是您可以添加或更改它。当前,表将以下类型的数据放入列表中:
-
Boolean
—带有复选框。 -
Number
-由右对齐的标签呈现。 -
Double
,Float
-与Number
相同,但是对象到文本的翻译是由NumberFormat实例执行的(使用当前语言环境的默认数字格式)。 -
Date
—由标签呈现,并由DateFormat实例执行对象到文本的转换(使用简短的日期和时间样式)。 -
ImageIcon
,Icon
-由居中的标签呈现。 -
Object
—由显示对象的字符串 值的标签呈现。
使用类似的算法选择单元格编辑器。
请记住,如果让表创建自己的模型,则它将Object
用作每一列的类型。要指定更精确的列类型,表模型必须适当地定义getColumnClass
方法,如TableDemo.java所示。
请记住,尽管渲染器确定每个单元格或列标题的外观并可以指定其工具提示文本,但是渲染器不会处理事件。如果需要拾取表内发生的事件,则使用的技术会因您感兴趣的事件种类而异:
Situation | 如何获得活动 |
---|---|
要检测来自正在编辑的单元的事件... | 使用单元格编辑器(或在单元格编辑器上注册一个侦听器)。 |
要检测行/列/单元格选择和取消选择... | 按照检测用户选择所述使用选择侦听器。 |
要检测列标题上的鼠标事件... | 在表的JTableHeader 对象上注册适当的mouse listener类型。 (有关示例,请参见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
不足,则可以使用另一个超类构建渲染器。最简单的方法是创建现有组件的子类,使您的子类实现TableCellRendererinterface。 TableCellRenderer
仅需要一种方法:getTableCellRendererComponent
。此方法的实现应设置呈现组件以反映传入的状态,然后返回该组件。
在TableDialogEditDemo
的snapshot中,用于“收藏夹颜色”单元格的渲染器是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的代码,该代码将ColorRenderer
实例注册为所有Color
数据的默认渲染器:
table.setDefaultRenderer(Color.class, new ColorRenderer(true));
要指定特定于单元格的渲染器,您需要定义一个JTable
子类,该子类将覆盖getCellRenderer
方法。例如,以下代码使表第一列中的第一个单元格使用自定义渲染器:
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 开始(下载 JDK 7 或更高版本)运行它。或者,要自己编译并运行示例,请参考example index。
源代码位于TableRenderDemo.java中。它使用以下代码将工具提示添加到“体育”列的单元格中:
//Set up tool tips for the sport cells.
DefaultTableCellRenderer renderer =
new DefaultTableCellRenderer();
renderer.setToolTipText("Click for combo box");
sportColumn.setCellRenderer(renderer);
尽管上一个示例中的工具提示文本是静态的,但是您也可以实现工具提示,其文本根据单元或程序的状态而变化。这有几种方法:
-
向渲染器的
getTableCellRendererComponent
方法的实现中添加一些代码。 -
覆盖
JTable
方法getToolTipText(MouseEvent)
。
将代码添加到单元格渲染器的示例在TableDialogEditDemo
中。单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行它。或者,要自己编译并运行示例,请参考example index。
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 value: " + newColor.getRed() + ", "
+ newColor.getGreen() + ", "
+ newColor.getBlue());
return this;
}
}
这是工具提示的示例:
您可以通过覆盖JTable
的getToolTipText(MouseEvent)
方法来指定工具提示文本。程序TableToolTipsDemo
显示了如何。单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行它。或者,要自己编译并运行示例,请参考example index。
带有“工具提示”的单元格位于“运动”和“素食”列中。这是其工具提示的图片:
以下是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;
}
...
}
该代码非常简单,除了可能调用convertColumnIndexToModel
。该调用是必需的,因为如果用户在四处移动列,则该列的视图索引将与该列的模型索引不匹配。例如,用户可能会拖动 素食 列(模型认为该列位于索引 4),因此它显示为第一列(视图索引为 0)。由于prepareRenderer
提供了视图索引,因此您需要翻译视图索引到模型索引,这样就可以确保已选择了预期的列。
为列标题指定工具提示
您可以通过为表格的JTableHeader
设置工具提示文本来将工具提示添加到列标题。通常,不同的列标题需要不同的工具提示文本。您可以通过覆盖表格标题的getToolTipText
方法来更改文本。或者,您可以调用TableColumn.setHeaderRenderer
为标题提供自定义渲染器。
对所有列标题使用相同的工具提示文本的示例在TableSorterDemo.java中。它是如何设置工具提示文本的:
table.getTableHeader().setToolTipText(
"Click to sort; Shift-Click to sort in reverse order");
TableToolTipsDemo.java提供了一个实现列标题工具提示的示例,该提示因列而异。如果使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableToolTipsDemo
(单击“启动”按钮)。或者,要自己编译并运行示例,请参考example index。
当您将鼠标悬停在除前两个列标题之外的任何列标题上时,您都会看到工具提示。名称栏不提供任何工具提示,因为它们似乎不言自明。这是列标题工具提示之一的图片:
以下代码实现了工具提示。基本上,它将创建一个JTableHeader
的子类,该子类将覆盖getToolTipText(MouseEvent)
方法,以便它返回当前列的文本。为了将修订后的表头与表相关联,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];
}
};
}
};
排序和过滤
表排序和筛选由排序器对象 管理。提供排序对象的最简单方法是将autoCreateRowSorter
bound 属性设置为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对象对其行进行排序。实现此interface的类必须提供一个名为compare
的方法,该方法定义如何比较两个对象以进行排序。例如,以下代码创建一个Comparator
,该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
try依次应用以下每个规则。规则遵循以下 Sequences;使用为排序器提供Comparator
的第一条规则,其余规则被忽略。
-
如果通过调用setComparator指定了一个比较器,请使用该比较器。
-
如果表模型报告列数据由字符串 组成(该列的
TableModel.getColumnClass
返回String.class
),请使用比较器,该比较器根据当前语言环境对字符串 进行排序。 -
如果
TableModel.getColumnClass
返回的列类实现Comparable
,请使用比较器,该比较器根据Comparable.compareTo返回的值对字符串 进行排序。 -
如果通过调用setStringConverter为表指定了字符串 转换器,请使用比较器,该比较器根据当前语言环境对结果字符串 表示形式进行排序。
-
如果没有上述任何规则,请使用比较器,该比较器在列数据上调用
toString
并根据当前语言环境对结果字符串 进行排序。
要进行更复杂的排序,请子类TableRowSorter
或其父类javax.swing.DefaultRowSorter。
要指定列的排序 Sequences 和排序优先级,请调用setSortKeys。这是一个示例,该示例按前两列对示例中使用的表进行排序。排序中各列的优先级由排序键列表中排序键的 Sequences 指示。在这种情况下,第二列具有第一个排序键,因此它们的行将按名字和姓氏排序。
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返回基于regular expression过滤的RowFilter
。
在下面的示例代码中,您显式创建一个 sorter 对象,以便以后可以使用它指定过滤器:
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()
。当用户 Importing 复杂的正则表达式时,try...catch
可以防止语法异常干扰 Importing。
当表使用排序器时,用户看到的数据可能与数据模型指定的 Sequences 不同,并且可能不包括数据模型指定的所有行。用户实际看到的数据称为视图,并具有自己的一组坐标。 JTable
提供了从模型坐标转换为视图坐标convertColumnIndexToView和convertRowIndexToView以及从视图坐标转换为模型坐标convertColumnIndexToModel和convertRowIndexToModel的方法。
NOTE:
使用分类器时,请始终记住要转换单元格坐标。
以下示例汇集了本节中讨论的想法。 TableFilterDemo.java对TableDemo
添加了少量更改。其中包括本节前面的代码段,这些代码段为主表提供了排序程序,并使用文本字段提供了过滤正则表达式。以下屏幕截图显示了TableFilterDemo
,未完成任何排序或过滤。请注意,模型中的第 3 行仍然与视图中的第 3 行相同:
如果用户在第二列上单击两次,则第四行成为第一行-但仅在视图中:
如前所述,用户在“过滤器文本”文本字段中 Importing 的文本定义了一个过滤器,该过滤器确定显示哪些行。与排序一样,过滤可能导致视图坐标与模型坐标偏离:
这是更新状态字段以反映当前选择的代码:
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));
}
}
}
);
使用组合框作为编辑器
设置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));
这是正在使用的组合框编辑器的图片:
前面的代码来自TableRenderDemo.java。您可以使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableRenderDemo
(单击“启动”按钮)。或者,要自己编译并运行示例,请参考example index。
使用其他编辑器
无论是为单元格的单列设置编辑器(使用TableColumn
setCellEditor
方法),还是为特定类型的数据设置编辑器(使用JTable
setDefaultEditor
方法),都可以使用坚持TableCellEditor
interface的参数来指定编辑器。幸运的是,DefaultCellEditor
类实现了此interface并提供了构造函数,使您可以指定JTextField
,JCheckBox
或JComboBox
的编辑组件。通常,您不必显式指定复选框作为编辑器,因为具有Boolean
数据的列会自动使用复选框渲染器和编辑器。
如果要指定除文本字段,复选框或组合框以外的编辑器怎么办?由于DefaultCellEditor
不支持其他类型的组件,因此您必须做更多的工作。您需要创建一个实现TableCellEditorinterface的类。 AbstractCellEditor类是一个很好的超类。它实现了TableCellEditor
的superinterfaceCellEditor,从而省去了实现单元格编辑器所需的事件触发代码的麻烦。
您的单元格编辑器类需要定义至少两个方法getCellEditorValue
和getTableCellEditorComponent
。 CellEditor
所需的getCellEditorValue
方法返回单元格的当前值。 TableCellEditor
所需的getTableCellEditorComponent
方法应配置并返回要用作编辑器的组件。
这是带有对话框的表的图片,该对话框间接用作单元格编辑器。当用户开始在“收藏夹颜色”列中编辑单元格时,将出现一个按钮(true 的单元格编辑器)并弹出对话框,用户可以使用该对话框选择其他颜色。
您可以使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableDialogEditDemo
(单击“启动”按钮)。或者,要自己编译并运行示例,请参考example index。
这是从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
的调用。没有此调用,即使不再显示 Pattern 对话框,编辑器也将保持活动状态。对fireEditingStopped
的调用使该表知道它可以停用编辑器,从而使该单元格再次由渲染器处理。
使用编辑器验证用户 Importing 的文本
如果单元格的默认编辑器允许 Importing 文本,则如果单元格的类型指定为String
或Object
以外的其他类型,则可以免费进行一些错误检查。错误检查是将 Importing 的文本转换为适当类型的对象的副作用。
当默认编辑器try创建与该单元格的列关联的类的新实例时,将自动检查用户 Importing 的字符串。默认编辑器使用接受String
作为参数的构造函数创建此实例。例如,在单元格类型为Integer
的列中,当用户键入“ 123”时,默认编辑器将使用等效于new Integer("123")
的代码创建相应的Integer
。如果构造函数抛出异常,则单元格的轮廓变为红色,并拒绝让焦点移出单元格。如果实现一个用作列数据类型的类,并且您的类提供了一个采用String
类型的单个参数的构造函数,则可以使用默认编辑器。
如果您希望使用文本字段作为单元格的编辑器,但想对其进行自定义(例如可能要更严格地检查用户 Importing 的文本,或者在文本无效时做出不同的反应),则可以更改单元格编辑器以使用格式化文本字段。格式化的文本字段可以在用户键入时或在用户指示键入结束之后(例如,按 Enter 键)连续检查该值。
以下代码取自名为TableFTFEditDemo.java的演示,它设置了一个格式化的文本字段作为编辑器,该字段将所有整数值限制在 0 到 100 之间。您可以使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableFTFEditDemo
(单击“启动”按钮)。或者,要自己编译并运行示例,请参考example index。
以下代码使格式化的文本字段成为包含Integer
类型数据的所有列的编辑器。
table.setDefaultEditor(Integer.class,
new IntegerEditor(0, 100));
IntegerEditor
类实现为DefaultCellEditor的子类,该子类使用JFormattedTextField
而不是DefaultCellEditor
支持的JTextField
。为此,它首先使用如何使用格式化的文本字段中描述的 API,将格式文本字段设置为使用整数格式并具有指定的最小值和最大值,以实现此 Object。然后,它将覆盖getTableCellEditorComponent
,getCellEditorValue
和stopCellEditing
方法的DefaultCellEditor
实现,并添加格式化文本字段所需的操作。
在显示编辑器之前,对getTableCellEditorComponent
的覆盖将设置格式化文本字段的* value 属性(而不仅仅是它从JTextField
继承的 text *属性)。覆盖getCellEditorValue
会将单元格值保留为Integer
,而不是格式化文本字段的解析器倾向于返回的Long
值。最后,通过覆盖stopCellEditing
可以检查文本是否有效,从而有可能阻止编辑器被关闭。如果文本无效,则您对stopCellEditing
的实现将弹出一个对话框,使用户可以选择 continue 编辑或恢复为上一个好的值。源代码有点 Long,无法包含在此处,但是您可以在IntegerEditor.java中找到它。
Printing
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
会弹出一个标准的打印对话框。 (在无头应用程序中,仅打印表.)返回值指示用户是 continue 打印作业还是取消打印作业。 JTable.print
可以抛出java.awt.print.PrinterException
,即checked exception;这就是上面的示例使用try ... catch
的原因。
JTable
提供了print
的多个重载选项。 TablePrintDemo.java中的以下代码显示了如何定义页面标题:
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
的更多信息,请参考2D Graphics路径中的Printing类。
使用表格的示例
下表列出了使用JTable
的示例,并在其中描述了这些示例。
Example | Where Described | Notes |
---|---|---|
SimpleTableDemo | 创建一个简单的表 | * no *自定义模型的基本表。不包含指定列宽或检测用户编辑的代码。 |
SimpleTable- SelectionDemo | 检测用户选择 | 将单个选择和选择检测添加到SimpleTableDemo 。通过修改程序的ALLOW_COLUMN_SELECTION 和ALLOW_ROW_SELECTION 常量,您可以try使用表默认值(仅允许选择行)的替代方法。 |
TableDemo | 创建表模型 | 具有自定义模型的基本表。 |
TableFTFEditDemo | 使用编辑器验证用户 Importing 的文本 | 修改TableDemo 以对所有Integer 数据使用自定义编辑器(带格式的文本字段变体)。 |
TableRenderDemo | 使用组合框作为编辑器 | 修改TableDemo 以对“体育”列中的所有数据使用自定义编辑器(组合框)。还可以智能地选择列大小。使用渲染器显示运动单元的工具提示。 |
TableDialogEditDemo | 使用其他编辑器 | 修改TableDemo ,使其具有显示颜色的单元格渲染器和编辑器,并允许您使用颜色 selectors 对话框选择新的颜色。 |
TableToolTipsDemo | 指定单元格的工具提示, 指定列标题的工具提示, | 演示如何使用多种技术来设置单元格和列标题的工具提示文本。 |
TableSortDemo | 排序和过滤 | 演示默认的排序器,该排序器使用户可以通过单击标题来对列进行排序。 |
TableFilterDemo | 排序和过滤 | 演示排序和过滤,以及如何使视图坐标与模型坐标偏离。 |
TablePrintDemo | Printing | 演示表格打印。 |
ListSelectionDemo | 如何编写列表选择监听器 | 显示如何使用表和列表之间共享的列表选择侦听器来使用所有列表选择 Pattern。 |
SharedModelDemo | Nowhere | 构建在ListSelectionDemo 的基础上,使数据模型在表和列表之间共享。如果您在表的第一列中编辑项目,则新值将反映在列表中。 |