使用 JTable 类你能够显示表中的数据,也能够容许用户编辑表中的数据,JTable 不包含数据,也不缓存数据,它只是你的数据的壹個视图,下图是一个在滚动窗格中显示的一个典型的表格: html
本节的其他部分告诉你如何完成一些表格相关的经常使用任务。
java
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)这些构造方法的优势就是它们很是易用,然而,这些构造方法也有它的缺点:
以下是壹段典型的建立壹個卷动窗格做为表格的容器的代码: 算法
JScrollPane scrollPane = new JScrollPane(table); table.setFillsViewportHeight(true);上面两行代码作了以下事情:调用 JScrollPane 构造方法并传入 table 对象做为其参数,这句话为 table 对象建立了壹個滚动窗格,table 被自动添加到该容器中;
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() 方法。
在这個默认配置下,表格支持选择壹行或者多行,用户能够选择连续的几行。最后一个单元格指示的用户获得一个特殊的迹象,在金属的视觉上看,这個元素超出了轮廓,它有时被称做焦点元素或者当前元素,用户使用鼠标或者键盘来作出选择,具体描述见下表: api
操做 |
鼠标动做 |
键盘动做 |
选择单行 |
单击 |
上下光标键 |
延伸的连续选择 |
Shift + 单击 或者在表格行上拖动 |
Shift + 上下光标键 |
添加行选择/切换行选择 |
Ctrl + 单击 |
移动控制向上箭头或下箭头选择控制铅,而后使用空格键添加到选择或控制,空格键切换行选择。 |
这個样例程序展现了类似的表格,容许用户操纵特定的 JTable 选项。它还有壹個文本面板记录选择事件。在下方的截图中,壹個用户运行程序,单击第壹行,而后 Ctrl + 单击第三行,注意点击的最后壹個单元格周围的轮廓,这就是突出率先选择的金属外观和感受。 数组
在"Selection Mode"下方有几個单选按钮,点击标签是"Single Selection"的按钮。如今你能够每次只选择壹行了。若是你点击标签是"Single Interval Selection"的单选按钮,你能够选择连续的壹组行记录。全部的"Selection Mode"下方的单选按钮都调用了方法 JTable.setSelectionMode 这個方法只接受壹個参数,且必须是类 javax.swing.ListSelectionModel 中的常量值之壹,以下所示:MULTIPLE_INTERVAL_SELECTION,SINGLE_INTERVAL_SELECTION 还有 SINGLE_SELECTION。 缓存
回到 TableSelectionDemo,注意在"Selection Options"下方的三個复选框,每個复选框控制壹個 boolean 型的由 JTable 定义的绑定变量: oracle
"行选择" 控制行选择,对应的有两個方法 setRowSelectionAllowed() 和 getRowSelectionAllowed()。当这個绑定属性为 true,而且 columnSelectionAllowed 的属性值为 false 时,用户能够选择行记录;
"列选择" 控制列选择,对应的有两個方法 setColumnSelectionAllowed() 和 getColumnSelectionAllowed()。当这個绑定属性为 true,而且 rowSelectionAllowed 绑定属性为 false 时,用户能够选择列记录;
"单元格选择" 控制单元格选择,对应的有两個方法 setCellSelectionEnabled() 和 getCellSelectionEnabled()。当这個绑定属性为 true 时,用户能够选择单個单元格或者矩形块状的单元格;
你一样可能注意到更改任意三個选项都会影响到其它选项,这是由于同时容许行选择和列选择和容许单元格选择彻底壹样,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 对象必须实现 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)); } } } );
启用壹個下拉列表做为编辑器很容易,如同下述样例展现的壹样。
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 类实现这个接口,并容许你指定编辑组成部分是一个JTextField,JCheckBox 或者 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 ,转载请注明原始出处。