如何使用表格

使用JTable类,您可以显示数据表,可以选择允许用户编辑数据。 JTable不包含或缓存数据;它只是您的数据视图。这是滚动窗格中显示的典型表的图片:

TableDemo 的快照,显示典型表。

本节的其余部分将向您展示如何完成一些与表相关的常见任务。以下是本节涵盖的主题:

创建简单表

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 *;有时称为“具有焦点的单元格”或“当前单元格”。

用户使用鼠标和/或键盘进行选择,如下表所示:

OperationMouse ActionKeyboard Action
选择单行。Click.向上箭头或向下箭头。
扩展连续选择。按住 Shift 单击或拖动行。上移箭头或下移箭头。
将行添加到选择/切换行选择。Control-Click使用 Control-上箭头或 Control-下箭头移动线索选择,然后使用空格键添加到选择中或使用 Control-空格键切换行选择。

要查看选择如何工作,请单击“启动”按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行TableSelectionDemo。或者,要自己编译并运行示例,请参考example index

该示例程序显示了熟悉的表,并允许用户操纵某些 JTable 选项。还有一个文本窗格,记录选择事件。

在下面的屏幕快照中,用户已运行该程序,在第一行中单击,然后在第三行中按住 Control 键单击。注意最后单击的单元格周围的轮廓;这就是 Metal 外观如何突出选择线索的方式。

具有不连续的行选择的 TableSelectionDemo。

在“选择 Pattern”下,有一组单选按钮。单击标记为“单一选择”的一个。现在您一次只能选择一行。如果单击“单个间隔选择”单选按钮,则可以选择一组必须连续的行。

“选择 Pattern”下的所有单选按钮都调用JTable.setSelectionMode。此方法采用单个参数,该参数必须是在javax.swing.ListSelectionModel中定义的以下常量之一:MULTIPLE_INTERVAL_SELECTIONSINGLE_INTERVAL_SELECTIONSINGLE_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设置为一个值具有将rowSelectionEnabledcolumnSelectionEnabled都设置为该值的副作用。将rowSelectionEnabledcolumnSelectionEnabled都设置为一个值也具有将cellSelectionEnabled设置为该值的副作用。将rowSelectionEnabledcolumnSelectionEnabled设置为不同的值也会产生将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方法之一。

MethodChange
fireTableCellUpdated指定单元格的更新。
fireTableRowsUpdated指定行的更新
fireTableDataChanged整个表的更新(仅数据)。
fireTableRowsInserted插入了新行。
fireTableRowsDeleted现有行已删除
fireTableStructureChanged无效整个表,包括数据和结构。

概念:编辑器和渲染器

在 continue 下几个任务之前,您需要了解表如何绘制其单元格。您可能希望表中的每个单元格都是一个组件。但是,出于性能原因,Swing 表的实现方式有所不同。

相反,通常使用单个“单元格渲染器”绘制包含相同类型数据的所有单元格。您可以将渲染器视为可配置的墨水戳,表格使用该墨水戳将格式正确的数据标记到每个单元格上。当用户开始编辑单元格的数据时,“单元格编辑器”将接管该单元格,从而控制该单元格的编辑行为。

例如,TableDemo的“年数”列中的每个单元格都包含Number数据,具体来说就是Integer对象。默认情况下,包含Number的列的单元格渲染器使用单个JLabel实例在列的单元格上绘制右对齐的适当数字。如果用户开始编辑单元格之一,则默认的单元格编辑器将使用右对齐的JTextField来控制单元格编辑。

要选择在列中显示单元格的渲染器,表格首先确定您是否为该特定列指定了渲染器。如果没有,则表将调用表模型的getColumnClass方法,该方法将获取列单元格的数据类型。接下来,该表将列的数据类型与为其注册了单元格渲染器的数据类型列表进行比较。该列表由表初始化,但是您可以添加或更改它。当前,表将以下类型的数据放入列表中:

  • Boolean —带有复选框。

  • Number-由右对齐的标签呈现。

  • DoubleFloat-与Number相同,但是对象到文本的翻译是由NumberFormat实例执行的(使用当前语言环境的默认数字格式)。

  • Date —由标签呈现,并由DateFormat实例执行对象到文本的转换(使用简短的日期和时间样式)。

  • ImageIconIcon-由居中的标签呈现。

  • Object —由显示对象的字符串 值的标签呈现。

使用类似的算法选择单元格编辑器。

请记住,如果让表创建自己的模型,则它将Object用作每一列的类型。要指定更精确的列类型,表模型必须适当地定义getColumnClass方法,如TableDemo.java所示。

请记住,尽管渲染器确定每个单元格或列标题的外观并可以指定其工具提示文本,但是渲染器不会处理事件。如果需要拾取表内发生的事件,则使用的技术会因您感兴趣的事件种类而异:

Situation如何获得活动
要检测来自正在编辑的单元的事件...使用单元格编辑器(或在单元格编辑器上注册一个侦听器)。
要检测行/列/单元格选择和取消选择...按照检测用户选择所述使用选择侦听器。
要检测列标题上的鼠标事件...在表的JTableHeader对象上注册适当的mouse listener类型。 (有关示例,请参见TableSorter.java。)
要检测其他事件...JTable对象上注册适当的侦听器。

接下来的几节将告诉您如何通过指定渲染器和编辑器来自定义显示和编辑。您可以按列或按数据类型指定单元格渲染器和编辑器。

使用自定义渲染器

本节介绍如何创建和指定单元格渲染器。您可以使用JTable方法setDefaultRenderer设置特定于类型的单元格渲染器。要指定特定列中的单元格应使用渲染器,请使用TableColumn方法setCellRenderer。您甚至可以通过创建JTable子类来指定特定于单元格的渲染器。

自定义默认渲染器DefaultTableCellRenderer渲染的文本或图像很容易。您只需创建一个子类并实现setValue方法,以便它使用适当的字符串 或图像调用setTextsetIcon。例如,以下是默认日期呈现器的实现方式:

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。此方法的实现应设置呈现组件以反映传入的状态,然后返回该组件。

TableDialogEditDemosnapshot中,用于“收藏夹颜色”单元格的渲染器是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);
    }
};

指定单元格的工具提示

默认情况下,为表格单元格显示的工具提示文本由单元格的渲染器确定。但是,有时通过覆盖JTablegetToolTipText(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;
    }
}

这是工具提示的示例:

TableDialogEditDemo 带有描述鼠标悬停单元格的 RGB 值的工具提示

您可以通过覆盖JTablegetToolTipText(MouseEvent)方法来指定工具提示文本。程序TableToolTipsDemo显示了如何。单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行它。或者,要自己编译并运行示例,请参考example index

带有“工具提示”的单元格位于“运动”和“素食”列中。这是其工具提示的图片:

TableToolTipsDemo,带有“运动”列中单元格的工具提示

以下是TableToolTipsDemo.java中的代码,这些代码为 SportVegetarian 列中的单元实现了工具提示:

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

当您将鼠标悬停在除前两个列标题之外的任何列标题上时,您都会看到工具提示。名称栏不提供任何工具提示,因为它们似乎不言自明。这是列标题工具提示之一的图片:

TableToolTipsDemo 带有列标题的工具提示

以下代码实现了工具提示。基本上,它将创建一个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中得到了展示:

单击姓氏后,TableSortDemo

为了更好地控制排序,您可以构造一个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

为了确定要对列使用ComparatorTableRowSortertry依次应用以下每个规则。规则遵循以下 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提供了从模型坐标转换为视图坐标convertColumnIndexToViewconvertRowIndexToView以及从视图坐标转换为模型坐标convertColumnIndexToModelconvertRowIndexToModel的方法。

NOTE:

使用分类器时,请始终记住要转换单元格坐标。

以下示例汇集了本节中讨论的想法。 TableFilterDemo.javaTableDemo添加了少量更改。其中包括本节前面的代码段,这些代码段为主表提供了排序程序,并使用文本字段提供了过滤正则表达式。以下屏幕截图显示了TableFilterDemo,未完成任何排序或过滤。请注意,模型中的第 3 行仍然与视图中的第 3 行相同:

TableFilterDemo 不排序

如果用户在第二列上单击两次,则第四行成为第一行-但仅在视图中:

TableFilterDemo 在第二列中进行反向排序

如前所述,用户在“过滤器文本”文本字段中 Importing 的文本定义了一个过滤器,该过滤器确定显示哪些行。与排序一样,过滤可能导致视图坐标与模型坐标偏离:

具有过滤功能的 TableFilterDemo

这是更新状态字段以反映当前选择的代码:

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方法),都可以使用坚持TableCellEditorinterface的参数来指定编辑器。幸运的是,DefaultCellEditor类实现了此interface并提供了构造函数,使您可以指定JTextFieldJCheckBoxJComboBox的编辑组件。通常,您不必显式指定复选框作为编辑器,因为具有Boolean数据的列会自动使用复选框渲染器和编辑器。

如果要指定除文本字段,复选框或组合框以外的编辑器怎么办?由于DefaultCellEditor不支持其他类型的组件,因此您必须做更多的工作。您需要创建一个实现TableCellEditorinterface的类。 AbstractCellEditor类是一个很好的超类。它实现了TableCellEditor的superinterfaceCellEditor,从而省去了实现单元格编辑器所需的事件触发代码的麻烦。

您的单元格编辑器类需要定义至少两个方法getCellEditorValuegetTableCellEditorComponentCellEditor所需的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 文本,则如果单元格的类型指定为StringObject以外的其他类型,则可以免费进行一些错误检查。错误检查是将 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。然后,它将覆盖getTableCellEditorComponentgetCellEditorValuestopCellEditing方法的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的示例,并在其中描述了这些示例。

ExampleWhere DescribedNotes
SimpleTableDemo创建一个简单的表* no *自定义模型的基本表。不包含指定列宽检测用户编辑的代码。
SimpleTable- SelectionDemo检测用户选择将单个选择和选择检测添加到SimpleTableDemo。通过修改程序的ALLOW_COLUMN_SELECTIONALLOW_ROW_SELECTION常量,您可以try使用表默认值(仅允许选择行)的替代方法。
TableDemo创建表模型具有自定义模型的基本表。
TableFTFEditDemo使用编辑器验证用户 Importing 的文本修改TableDemo以对所有Integer数据使用自定义编辑器(带格式的文本字段变体)。
TableRenderDemo使用组合框作为编辑器修改TableDemo以对“体育”列中的所有数据使用自定义编辑器(组合框)。还可以智能地选择列大小。使用渲染器显示运动单元的工具提示。
TableDialogEditDemo使用其他编辑器修改TableDemo,使其具有显示颜色的单元格渲染器和编辑器,并允许您使用颜色 selectors 对话框选择新的颜色。
TableToolTipsDemo指定单元格的工具提示, 指定列标题的工具提示,演示如何使用多种技术来设置单元格和列标题的工具提示文本。
TableSortDemo排序和过滤演示默认的排序器,该排序器使用户可以通过单击标题来对列进行排序。
TableFilterDemo排序和过滤演示排序和过滤,以及如何使视图坐标与模型坐标偏离。
TablePrintDemoPrinting演示表格打印。
ListSelectionDemo如何编写列表选择监听器显示如何使用表和列表之间共享的列表选择侦听器来使用所有列表选择 Pattern。
SharedModelDemoNowhere构建在ListSelectionDemo的基础上,使数据模型在表和列表之间共享。如果您在表的第一列中编辑项目,则新值将反映在列表中。