[翻译]如何使用 Swing 组件 JTable

使用 JTable 类你能够显示表中的数据,也能够容许用户编辑表中的数据,JTable 不包含数据,也不缓存数据,它只是你的数据的壹個视图,下图是一个在滚动窗格显示的一个典型的表格 html

本节其他部分告诉如何完成一些表格相关的经常使用任务
java

建立壹個简单的表格

一、点击 Launch 可使用 Java Web Start 运行 SimpleTableDemo ,或者由你本身来编译运行 SimpleTableDemo.java ;
二、点击包含 "Snowboarding" 的单元格,这壹行都会被选中,说明你已经选择了 Kathy Smith 的数据。壹個特殊的高亮显示说明 "Snowboarding" 单元格是能够编辑的。一般来说,你能够经过双击单元格来编辑该单元的文本信息。
三、将鼠标指向 "First Name",如今点击鼠标而且拖到右边,如同你所看到的壹样,用户能够从新排列表格中的列名称。
四、将鼠标指向列头的右边缘,如今点击鼠标而且左右拖动,该列宽度发生了改变,而且其它列填充了多出来的空间。
五、调整包含表格的窗口大小,以便它比实际展现须要的空间更大壹些。全部的表格元素变宽了,水平的填充了多出来的额外空间。

SimpleTableDemo.java 中的表格在 String 数组中声明列名称:
String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};

它的数据被初始化而且存储在壹個二维的对象数组中: web

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)}
		};
而后表格对象使用 data 和 columnNames 完成构建。
JTable table = new JTable(data, columnNames);

壹共有两個 JTable 构造方法直接接受数据,SimpleTableDemo 使用第壹种: 正则表达式

JTable(Object[][] rowData, Object[] columnNames)
JTable(Vector rowData, Vector columnNames)
这些构造方法的优势就是它们很是易用,然而,这些构造方法也有它的缺点:
他们设置每個单元格可编辑;
他们以相同的方式对待全部的数据类型(所有识别为 String)。好比说,若是表中的某壹列包含 Boolean 数据,表格能够将该数据显示为壹個复选框。所以,若是你使用上述两個构造方法中任意壹個,你的 Boolean 数据都会被显示为字符串。你能够在上图中的 Vegetarian 列中查看这种差别。
他们要求你把全部的数据都存储在壹個二维数组或者 Vector 对象中,可是这样作对于某些状况下的数据是不合适的。好比说,若是你从数据库中初始化壹系列数据,你可能但愿直接查询对象以获取结果,而不是把全部的结果都复制到数组或者 Vector 中。
若是你但愿绕过这些限制,你须要实现本身的 Table Model,能够参考 建立壹個 Table Model

增长壹個表格到容器中

以下是壹段典型的建立壹個卷动窗格做为表格的容器的代码: 算法

JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
上面两行代码作了以下事情:调用 JScrollPane 构造方法并传入 table 对象做为其参数,这句话为 table 对象建立了壹個滚动窗格,table 被自动添加到该容器中;
调用 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(int) 方法改变表格的宽度调整行为。

用户的选择

在这個默认配置下,表格支持选择壹行或者多行,用户能够选择连续的几行。最后一个单元格指示的用户获得一个特殊的迹象,在金属的视觉上看,这個元素超出了轮廓,它有时被称做焦点元素或者当前元素,用户使用鼠标或者键盘来作出选择,具体描述见下表: api

操做    
鼠标动做
键盘动做
选择单行
单击    
上下光标键
延伸的连续选择
Shift + 单击 或者在表格行上拖动
Shift + 上下光标键
添加行选择/切换行选择
Ctrl + 单击
移动控制向上箭头或下箭头选择控制铅,而后使用空格键添加到选择或控制,空格键切换行选择。

要查看选择行是如何执行的,请点击 Launch 以使用 Java™ Web Start 来运行 TableSelectionDemo.java,或者由你本身来编译运行这個 例子

这個样例程序展现了类似的表格,容许用户操纵特定的 JTable 选项。它还有壹個文本面板记录选择事件。在下方的截图中,壹個用户运行程序,单击第壹行,而后 Ctrl + 单击第三行,注意点击最后壹個单元格周围的轮廓,这就是突出率先选择的金属外观和感受 数组

在"Selection Mode"下方有几個单选按钮,点击标签是"Single Selection"的按钮。如今你能够每次只选择壹行了。若是你点击标签是"Single Interval Selection"的单选按钮,你能够选择连续的壹组行记录。全部的"Selection Mode"下方的单选按钮都调用了方法 JTable.setSelectionMode 这個方法只接受壹個参数,且必须是类 javax.swing.ListSelectionModel 中的常量值之壹,以下所示:MULTIPLE_INTERVAL_SELECTIONSINGLE_INTERVAL_SELECTION 还有 SINGLE_SELECTION缓存

回到 TableSelectionDemo,注意在"Selection Options"下方的三個复选框,每個复选框控制壹個 boolean 型的由 JTable 定义的绑定变量: oracle

"行选择" 控制行选择,对应的有两個方法 setRowSelectionAllowed() 和 getRowSelectionAllowed()。当这個绑定属性为 true,而且 columnSelectionAllowed 的属性值为 false 时,用户能够选择行记录;
"列选择" 控制列选择,对应的有两個方法 setColumnSelectionAllowed() 和 getColumnSelectionAllowed()。当这個绑定属性为 true,而且 rowSelectionAllowed 绑定属性为 false 时,用户能够选择列记录;
"单元格选择" 控制单元格选择,对应的有两個方法 setCellSelectionEnabled() 和 getCellSelectionEnabled()。当这個绑定属性为 true 时,用户能够选择单個单元格或者矩形块状的单元格;

注意:JTable 使用很是简单的选择概念管理行和列的交叉点, 不是设计来处理彻底独立单元格选择的
若是你清除全部的三個复选框,将三個绑定属性设置为 false,那麽就不会有选择。只有高亮元素显示。
你可能会注意到在间隔选择模式下 "Cell Selection" 复选框被禁用。这是由于在演示中,不支持这种模式下的单元格选择。你能够在间隔选择模式下指定单元格的选择,可是结果集是壹個不会产生任何有效选择的表格。

你一样可能注意到更改任意三個选项都会影响到其它选项,这是由于同时容许行选择和列选择和容许单元格选择彻底壹样,JTable 自动更新了三個绑定变量以保持它们的壹致性。

注意:设置 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());

使用选择功能生成壹系列的事件。请参考 编写事件监听器 课程的 如何编写壹系列选择事件监听器 这壹节。

注意:选择数据实际描述为在视图中选择单元格 (表格数据就像它排序或者过滤以后显示出来的壹样) 而不是选择表格的 Model。这個区别不会影响你,除非你查看从新排列的数据,包括排序,过滤或者用户操做过的行。在这种状况下,你必须使用在排序和过滤中提到的转换方法转换选择坐标。

建立壹個 Table Model

每個表对象使用壹個 table model 对象来管理实际的表数据。table model 对象必须实现 TableModel 接口。若是开发人员不提供 table model 对象的定义,那么 JTable 会自动建立壹個 DefaultTableModel 类的实例,这种关系以下图所示。

用 SimpleTableDemo 做为 table model 构造 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);
    }
}
如同上述代码所示,实现壹個 table model 能够很简单。一般来说,你能够在 AbstractTableModel 的子类里来实现你本身的 Table Model。你的 model 可能把数据保存在 Array,Vector 或者哈希表中,或者它也能够从外部源获取数据,好比说从数据库获取数据。它甚至能够在运行期间生成表数据。

这個表格与 SimpleTableDemo 在以下几個地方存在不一样点:
一、TableDemo 的自定义 table model 哪怕很简单,它也能够决定数据的类型,帮助 JTable 以更好的格式展现数据。从另壹方面讲,SimpleTableDemo 自动建立的 table model 并不知道列 # of Years 包含靠右对齐的数字,而且具备特殊的格式。它不知道列 Vegerarian 包含布尔型值,能够将其展现成复选框。
二、在 TableDemo 中实现的自定义 table model 不容许你编辑姓名列,然而它容许你编辑其它列。在 SimpleTableDemo 中,全部的元素都是能够编辑的。

能够看到来自 TableDemo.java 的以下代码和来自 SimpleTableDemo.java 的不壹样。黑体字显示建立表格的 model 与为建立 SimpleTableDemo 而自动生成的 table model 不壹样。 

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) {
        return col >= 2;
    }

    //若是你的表格中的数据不改变,则不须要实现这個方法
    public void setValueAt(Object value, int row, int col) {
        data[row][col] = value;
        fireTableCellUpdated(row, col);
    }
    ...
}

监控数据的更新

当表格中的数据发生改变时,每個 table model 均可以有壹系列的监听器,监听器都是 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);

        ...//对数据作壹些事情...
    }
    ...
}

发起数据更新事件

为了发起数据更新的事件,table model 必须知道如何建立壹個 TableModelEvent 对象。这能够是壹個复杂的过程,可是它如今已经在 DefaultTableModel 中实现了。你既能够容许 JTable 使用默认的DefaultTableModel 实例,也能够建立你本身的 DefaultTableModel 的自定义的子类。个人理解,发起数据更新事件的含义就是,当数据改变时,通知全部相关的监听器,让它们知道某行某列的数据发生了改变,就是这样。若是 DefaultTableModel 对于你自定义的 table model 类而言不适用,你就能够考虑继承 AbstractTableModel 类实现壹個子类。这個类为建立 TableModelEvent 对象实现了壹個简单的框架。你的自定义类只须要在每次表数据被外部源改变时,简单的调用以下的 AbstractTableModel 方法。

方法名
发生的变化
fireTableCellUpdated(int row, int column)
更新了某個单元格,其坐标为(row, column)
fireTableRowsUpdated(int firstRow, int lastRow)
更新了从 firstRow 到 lastRow 的行记录,包括 firstRow 和 lastRow
fireTableDataChanged()
更新了整個表格中的数据
fireTableRowsInserted(int firstRow, int lastRow)
在行的范围[firstRow, lastRow]内已经插入新的数据,包含 firstRow 和 lastRow
fireTableRowsDeleted(int firstRow, int lastRow)
在行范围[firstRow, lastRow]内存在的行被删除了,包含 firstRow 和 lastRow
fireTableStructureChanged()
整個表格失效,包括表中的数据和表的结构

相关内容:编辑器和渲染器

在你开始继续接下来几個任务以前,你须要理解表格是如何画出它们的单元格。你可能指望每個表格中的单元格都是壹個组件。然而,出于性能方面考虑,Swing 的表格的实现是不壹样的。
相反,单独壹個单元格渲染器一般能够用来画出全部的包含相同数据类型的单元格。你能够将渲染器想象成壹個可配置的墨水印章,表格使用它来给适当格式化的数据盖章。当用户开始编辑单元格中的数据时,壹個单元格渲染器接手单元格,控制单元格的编辑行为。
举例来讲,TableDemo 中的每個# of Years 列的单元格包含 Number 型数据,比较特殊,是 Integer 对象。默认状况下,单元格渲染器对于壹個包含数字的列,在每個列中的单元格上都使用壹個 JLable 实例来画出合适的数字,靠右对齐。若是用户开始编辑其中壹個单元格,默认的单元格编辑器使用靠右对齐的 JTextField 来控制单元格的编辑。
为了选择渲染器来显示列中的单元,你首先须要决定表格是否须要为特定的列使用特定的渲染器。若是不须要,那么表格调用 table model 的 getColumnClass 方法,后者将会获取列中元素的数据类型,下壹步,表格中列的数据类型与壹個数据类型列表比较,数据类型列表中的元素渲染器已经注册过。这個列表由表格来完成初始化,可是你能够增长或者改变它。目前,表格将以下类型的数据放进列表中:

Boolean — 被渲染成壹個复选框;
Number — 被渲染成壹個靠右对齐的标签;
Double, Float — 和 Number 类型壹样,可是壹個 NumberFormat 实例会完成对象到文本的转换,针对当前语言使用默认的数字格式;
Date — 被渲染成壹個标签,可是壹個 DateFormat 实例会完成对象到文本的转换,针对时间和日期使用壹种简短的风格;
ImageIcon, Icon — 被渲染成壹個居中对齐的标签;
Object — 被渲染成壹個显示对象的字符串值的标签;

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

记住,若是你容许壹個表格使用它本身的 model ,它就会使用 Object 做为每個列的类型。为了指定更加精确的列类型,table model 必须定义恰当的 getColumnClass() 方法。就像 TableDemo.java 的演示代码那样。牢记尽管渲染器决定每個单元格或者列头部如何展现,以及它们的 tool tip 文本是什么,可是渲染器不处理事件。若是你须要获取发生在表格中的事件,你须要使用技术不一样的你感兴趣的事件排序:

状况

如何获取事件
检测单元格被编辑的事件
使用单元格编辑器,或者在单元格编辑器上注册壹個监听器
检测行、列或者单元格的选择与反选事件
使用 Detecting User Selections 中提到的选择监听器
在列的头部检测鼠标事件
在表格的 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 还不够,你可使用另外壹個子类建立壹個渲染器。最简单的方法就是建立壹個已经存在的组件的子类,实现 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 是颜色的固定边缘
                //table.getSelectionBackground().
                setBorder(selectedBorder);
            } else {
                ...
                //unselectedBorder 是颜色的固定边缘
                //table.getBackground().
                setBorder(unselectedBorder);
            }
        }
        
        setToolTipText(...); //留待后继讨论
        return this;
    }
}
这里是来自 TableDialogEditDemo.java 的壹段代码注册了壹個 ColorRenderer 实例做为全部颜色的数据的默认渲染器:
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(MonseEvent e) 方法能够更加简单指定工具提示文本。这壹章节向你展现如何使用这两個技术。
为了在壹個单元格的渲染器上增长工具提示,你首先须要得到或者建立壹個单元格渲染器。而后,确保渲染器组件是壹個 JComponent,调用它的 setToolTipText() 方法便可。
TableRenderDemo 中有壹個为单元格设置工具提示的例子。点击这里能够运行该样例代码,固然你也能够本身编译运行它的源代码。它使用以下代码在 Sport 列中为单元格添加了工具提示:

DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
renderer.setToolTipText("Click for combo box");
sportColumn.setCellRenderer(renderer);

虽然上個例子里的工具提示文本样例是静态的,可是你也能够实现随着单元格状态改变而改变的工具提示文本程序,这里有两种实现的方法:
一、在渲染器的 getTableCellRendererComponent() 方法实现中增长壹些代码;
二、重载 JTable 的 getToolTipText(MonseEvent e)方法;

壹個在单元格渲染器中添加代码的例子是 TableDialogEditDemo。点击启动按钮能够运行这個例子,或者你也能够自行编译运行这個例子。TableDialogEditDemo 使用了壹個用于颜色的渲染器,而且在 ColorRender.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 e) 方法你能够指定工具栏提示文本,程序 TableToolTipsDemo 展现了该如何进行。Sport 和 Vegetarian 两個列中的元素具备工具栏提示,它们的演示效果以下:

这是来自 TableToolTipsDemo 中的壹段代码,它实现了在 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()该调用是必要的,由于若是用户移动周围的列,列的视图的索引将不匹配模型的索引例如,用户能够拖动 Vegetarian 假设该模型的列索引4使得它能够显示做为第一列 - 此时视图索引为0因为prepareRenderer提供视图索引您须要转换视图索引到模型索引这样你才能够确信已选定

为表头指定工具提示信息

经过为你的表格的 JTableHeader设置工具提示文本,你能够增长壹個工具提示到列的头部。一般不一样的列头部须要不一样的工具提示信息。经过重载表格头的 getToolTipText() 方法你能够改变文本信息。你能够交替的调用 TableColumn.setHeaderRenderer() 方法来为表头提供自定义的渲染器。
TableSorterDemo.java 中有壹個为全部列的头部使用工具提示文本的例子,以下是如何设置工具提示信息的代码:

table.getTableHeader().setToolTipText("Click to sort; Shift-Click to sort in reverse order");
TableToolTipsDemo.java 中实现了壹個根据列的头部给出不一样的工具提示信息的例子。除了最初的两個之外,当你的鼠标划过任何列的头部时,你会看见工具提示信息。由于他们彷佛不言自明,因此没有为这些名称列提供工具提示信息。


接下来的代码实现了工具提示功能,基本上,它建立了壹個 JTableHeader 的子类而且重载了 getToolTipText(MonseEvent e)方法,以便它能够为当前列返回文本。为了关联修改表的表头,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()) {
    ...

    //实现表头工具栏提示
    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];
            }
        };
    }
};

排序与过滤数据

表格的排序和过滤由壹個 sorter 对象进行管理,最简单的提供 sorter 对象的方法是设置 autoCreateRowSorter 绑定属性为 true:

JTable table = new JTable();
table.setAutoCreateRowSorter(true);

此次咱们定义壹個行排序 sorter,它是 javax.swing.table.TableRowSorter 的壹個实例。当用户点击列的头部时,排序工具提供给表格壹個简单的基于当地的排序。TableSortDemo.java 就是这样壹個演示的例子,下图是截屏:

要想获取更多关于排序的控制,你能够建立壹個 TableRowSorter 的实例而且指定它是你的表格中的排序对象。

TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(table.getModel());
table.setRowSorter(sorter);
TableRowSorter 使用 java.util.Comparator 对象来完成行排序。壹個实现了这样接口的类提供壹個 compare() 方法用于定义任意两個对象为了完成排序应该如何进行比较。举例来讲,下述的代码建立了壹個比较器,用于对比壹系列的字符串经过对比每個字符串最后壹個字母来进行排序:
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]);
    }
};
这個例子是特地简化过的,更有表明性的,壹個比较器的实现是壹個 java.text.Collator 的子类。你能够定义你本身的子类,使用在 Collator 类中的工厂方法来根据指定的语言得到壹個 Comparator 对象,或者使用 java.text.RuleBasedCollator 类。

这個例子是特意简化过的;更常见状况下,Comparator 的实现是 java.text.Collator 的壹個子类。你能够有你本身的子类,使用在 Collator 类的工厂方法能够获取壹個基于本地环境的 Comparator 实例,或者使用 java.text.RuleBasedCollator

对于每壹列应该使用哪一种 Comparator ,TableRowSorter 会尝试应用以下规则。规则会按照以下的顺序被遵循;第壹条规则若是已经使用,则后继的规则会被忽略。

一、若是已经经过 setComparator() 指定了壹個比较器,则使用该比较器。
二、若是 table model 已经声明列数据由字符串组成,就是说对于指定列调用 TableModel.getColumnClass() 方法返回 String.class,使用基于本地环境的字符串顺序进行排序。
三、若是针对列调用 TableModel.getColumnClass() 以后的返回值实现了 Comparable 接口,则使用 Comparable.compareTo() 方法的返回值的字符串顺序进行排序。
四、若是表格调用了 setStringConverter() 方法指定了字符串转换器,则使用基于本地环境的字符串的表现形式的字符串排序器排序。
五、若是以上规则均不适用,则先调用列中数据的 toString() 方法将其转换成字符串类型,而后针对转换后的字符串使用基于本地环境的字符串顺序进行排序。

对于更多更加复杂的排序类型,能够继承 TableRowSorteror 的父类 javax.swing.DefaultRowSorter

调用 setSortKeys() 能够指定列的排序顺序和排序优先级。这里有壹個针对表格的前两列进行排序的例子。优先的列能够在排序键的列表中指定。在这种状况下,第二列拥有第壹個排序键,因此这些行先按照 first name 排序,而后再按照 second name 排序。

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;
    try {
        rf = RowFilter.regexFilter(filterText.getText(), 0);
    } catch (java.util.regex.PatternSyntaxException e) {
        return;
    }
    sorter.setRowFilter(rf);
}

在后面的例子中,每次文本域改变时都会调用 newFilter() 方法。当用户输入难懂的正则表达式时,try...catch 会防止干扰和阻止语法端的输入异常。当表格使用了排序器,用户看到的数据可能呈现壹种与 table model 指定的不一样的顺序,也有可能包含 table model 所指定的全部数据。用户实际看到的数据被称做视图,它有本身的坐标系。JTable 提供了从 model 坐标系到视图坐标系的转换方法: convertColumnIndexToView()convertRowIndexToView() ,也提供了从视图坐标系到 model 坐标系的转换方法:convertColumnIndexToModel()convertRowIndexToModel()。

注意:当使用排序器时,总要记住转换单元格的坐标系。

接下来的例子把咱们这节讨论过的全部想法都带来了。TableFilterDemo.java 给 TableDemo 作了壹些小的改变。它们包含在这节以前的代码片断中,做用是为主表提供了壹個排序器,而且使用壹個文本框来提供过滤用的正则表达式。以下的截屏展现了 TableFilterDemo 在排序和过滤操做以前的样子。注意模型中的第三行在视图中也在相同的第三行:

若是用户在第二列上点击两次,第四列就变成了第壹列,但只是在视图中:

就像以前提到的那样,用户在"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("Selected Row in view: %d. " + "Selected Row in model: %d.", viewRow, modelRow));
			}
		}
	}
);

使用 ComboBox 下拉列表做为编辑器

启用壹個下拉列表做为编辑器很容易,如同下述样例展现的壹样。

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

使用其它编辑器

不管你是为单個的列或者单元格设置编辑器(使用TableColumnsetCellEditor的方法)仍是为特定的数据类型设置编辑器(使用JTablesetDefaultEditor的方法),你都应该让这個编辑器实现 TableCellEditor 接口。幸运的是,DefaultCellEditor 类实现这个接口并容许你指定编辑组成部分是一个JTextFieldJCheckBox 或者 JComboBox 的构造函数一般,您不须要明确指定一个复选框做为壹個编辑器由于布尔数据列会自动使用复选框渲染器和编辑器

若是你想指定复选框、下拉列表、文本框编辑器之外的文本字段该如何作呢因为DefaultCellEditor不支持其余类型的组件,您必须更多一点的工做您须要建立一个类实现 TableCellEditor 的接口 AbstractCellEditor是一个很好超类使用实现了 TableCellEditor超级接口,CellEditor减小了您的麻烦同时实现了事件触发单元格编辑器必需代码

你的单元格编辑器须要定义至少两個方法:getCellEditorValue() 和 getTableCellEditorComponent()。 方法 getCellEditorValue() 的定义是 CellEditor 所必需的,它返回单元格的当前值。方法 getTableCellEditorComponent() 则是 TableCellEditor 所必需的,应该配置为返回你但愿使用的那個编辑器的组件。

这里有壹個用对话框做为单元格编辑器(不是直接的出现)的表格的图片。当用户开始编辑列 Favorite Color 列中的单元格时,壹個按钮(真正的单元格编辑器)出现而且显示出对话框,在对话框中用户能够选择壹個不壹样的颜色值。

上述代码参见 TableDialogEditDemo.java

这是来自 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,  //模型
                                        colorChooser,
                                        this,  //确认按钮的 handler
                                        null); //没有取消按钮的 handler
    }

    public void actionPerformed(ActionEvent e) {
        if (EDIT.equals(e.getActionCommand())) {
            //用户点击了单元格,因此触发了弹出对话框
            button.setBackground(currentColor);
            colorChooser.setColor(currentColor);
            dialog.setVisible(true);

            fireEditingStopped(); //让渲染器从新可见

        } else { //用户点击了对话框中的 OK 按钮
            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做为参数构造函数建立这个实例例如,在一列Integer类型单元格中当用户输入“123”的默认编辑器建立相应的整数使用至关于新的整数(“123”代码若是构造函数抛出异常,单元格的轮廓变成红色拒绝焦点移动的单元格若是实现列的数据类型做为一类可使用默认的编辑若是你的类提供了一个接受一个参数类型为String构造函数

若是你想使用壹個文本框做为单元格的编辑器,可是想自定义它 — 可能须要更加严格的检查用户输入,或者当输入文本不合法时用不一样的方法重构 — 你可使用壹個 formatted text field 改变单元格编辑器。用户代表打字(如按Enter键结束以后,格式化的文本框能够连续检查用户键入后的值

接下来的代码来自于 TableFTFEditDemo.java ,它设置了壹個格式化文本框做为编辑器,后者限制全部的整型值都必须在 0 到 100 之间。接下来的代码为全部的列建立了壹個包含 Integer 类型数据的格式化文本框:
table.setDefaultEditor(Integer.class, new IntegerEditor(0, 100));

IntegerEditor 类是做为 DefaultCellEditor 的壹個子类实现的,而且使用了壹個 JFormattedTextField 代替了 JTextField,后者是由 DefaultCellEditor 支持的。它是经过首先设置壹個使用整型格式的格式化文本框,而且指定最小和最大值,而后使用 How to Use Formatted Text Fields 中提到的 API 实现的。以后它重载了 DefaultCellEditor 的getTableCellEditorComponent(),getCellEditorValue(), 和 stopCellEditing() 方法实现,为格式化文本框增长了必要的操做。

覆盖getTableCellEditorComponent()设置格式的文本字段的值属性(不只仅是文本属性继承自JTextField的编辑显示以前覆盖getCellEditorValue()保持做为一个整数单元格的值,而不是,比方说,Long值,格式化文本字段的解析器趋于恢复最后,覆盖stopCellEditing()让检查文本是否是有效的,可能中止编辑解雇若是文本是无效的,stopCellEditing()的实施提出了一个对话框,让用户选择继续编辑恢复到最后一个良好的价值源代码是太长了一点,包括在这里,你也能够查看 IntegerEditor.java

打印表格内容

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 应用上调用打印功能会打开壹個标准的打印对话框。在壹個没有表格头的应用中,则简单的打印它。它的返回值表示用户是否继续打印的任务仍是放弃打印。JTable.print() 能够抛出 java.awt.print.PrinterException 异常,它是壹個 受检异常 ,这就是为何咱们在例子中要使用壹個 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 的更多信息,你能够参考 Printing 这壹节课程,它出如今 2D Graphics 系列课程中。

本文的英文原文来自http://docs.oracle.com/javase/tutorial/uiswing/components/table.html ,中文翻译文章首发开源中国社区 http://my.oschina.net/bairrfhoinn/blog/166850 ,转载请注明原始出处。

相关文章
相关标签/搜索