在第17章中,我们深入了解了Swing的JTree组件。在本章中,我们将会探讨JTable组件的细节。该组件是用于以网络的形式显示二维数据的标准Swing组件。
18.1 Intoducing Tables
图18-1显示了JTable的一个简单示例。我们可以看到这个示例包含日本字体。为了能够看到本章示例程序中的Kanji表意文字,我们需要安装必须的日本字体。然而,在没有配置我们的环境来显示日本字体的情况下这些示例也可以正常工作,但是我们并不能看到表意文字,相反我们会看到问号或是方框,这依据于我们的平台而定。
类似于JTree组件,JTable组件依赖于各种用于其内部工作的支持类。对于JTable,支持类位于javax.swing.table包中。JTable中的单元可以通过行,列,行与列,或是单个单元来选定。当前 的ListSelectionModel设置负责控制表格的选定。
表格中不同单元格的显示是TableCellRenderer的职责;DefaultCellRenderer以JLabel子类的形式提供了一个TableCellRenderer接口的实现。
管理存储在单元格中的数据是通过TableModel接口实现来实现的。AbstractTableModel提供了提供了一个不具有数据存储功能的接口实现的基础。通过对比,DefaultTableModel封装了TableModel接口并且使用Vector对象用于数据存储。如果我们需要一个不同的存储类型而不是DefaultTableModel提供的类型,则我们需要扩展AbstractTableModel;例如,如果我们已经在我们自己的数据结构中存储了数据。
TableColumnModel接口与此接口的DefaultTableColumnModel实现将表格的数据作为一系列的列来管理。他们配合TableColumn类使用从而为单个列的管理提供更多的灵活性。例如,我们可以以不同于在JTable中的显示顺序来在TableModel中存储数据列。TableColumnModel管得另一个ListSelectionModel来控制表格列的选定。
在每一列的顶部是一个列头。默认情况下,TableColumn类依据JTableHeader类来渲染一个文本列头。然而,我们必须将JTable嵌入到一个滚动面板中来查看默认列头。
JTable中的单元格是可编辑的。如果一个单元格是可编辑的,编辑如何作用依赖于TableCellEditor实现,例如DefaultCellEditor实现,其扩展了AbstractCellEditor。另外,并不存在用于处理单个行的类。行必须通过单元格来处理。在幕后,JTable使用SizeSequence实用类来处理高度变化的行;我们并不需要自己来处理。
JTable组件所用的元素之间还存在一些其他的关系。这些关系会在本章稍后的特定接口与类中进行探讨。
要展示JTable元素如何组合在一起,请参看图18-2。
18.2 JTable类
首先我们来了解JTable类,他为我们提供了以表格的形式显示数据的方法。(参看图18-1与18-2)。
18.2.1 创建JTable
我们可以有七种不同的方法来创建JTable。这些构造函数可以使得我们使用多种数据源来创建表格。
public JTable() JTable table = new JTable(); public JTable(int rows, int columns) JTable table = new JTable(2, 3); public JTable(Object rowData[][], Object columnNames[]) Object rowData[][] = { { "Row1-Column1", "Row1-Column2", "Row1-Column3"}, { "Row2-Column1", "Row2-Column2", "Row2-Column3"} }; Object columnNames[] = { "Column One", "Column Two", "Column Three"}; JTable table = new JTable(rowData, columnNames); public JTable(Vector rowData, Vector columnNames) Vector rowOne = new Vector(); rowOne.addElement("Row1-Column1"); rowOne.addElement("Row1-Column2"); rowOne.addElement("Row1-Column3"); Vector rowTwo = new Vector(); rowTwo.addElement("Row2-Column1"); rowTwo.addElement("Row2-Column2"); rowTwo.addElement("Row2-Column3"); Vector rowData = new Vector(); rowData.addElement(rowOne); rowData.addElement(rowTwo); Vector columnNames = new Vector(); columnNames.addElement("Column One"); columnNames.addElement("Column Two"); columnNames.addElement("Column Three"); JTable table = new JTable(rowData, columnNames); public JTable(TableModel model) TableModel model = new DefaultTableModel(rowData, columnNames); JTable table = new JTable(model); public JTable(TableModel model, TableColumnModel columnModel) // Swaps column order TableColumnModel columnModel = new DefaultTableColumnModel(); TableColumn firstColumn = new TableColumn(1); firstColumn.setHeaderValue(headers[1]); columnModel.addColumn(firstColumn); TableColumn secondColumn = new TableColumn(0); secondColumn.setHeaderValue(headers[0]); columnModel.addColumn(secondColumn); JTable table = new JTable(model, columnModel); public JTable(TableModel model, TableColumnModel columnModel, ListSelectionModel selectionModel) // Set single selection mode ListSelectionModel selectionModel = new DefaultListSelectionModel(); selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JTable table = new JTable(model, columnModel, selectionModel);
无参数的构造函数会创建一个没有行与列的表格。第二个构造函数带有两个参数来创建一个具有行与列的空表。
注意,由JTable构造函数所创建的单元格是可编辑的,而不是只读的。要在代码中修改其内容,只需要调用JTable的public void setValueAt(Object value, int row, int column)方法。
当我们的数据已经位于一个特定的结构形式中时,接下来的两个方法就会十分有用。例如,如果我们的数据位于数组或是Vector对象中时,我们可以创建一个JTable而不需要创建我们自己的TableModel。一个两行三列的表格可以使用数组 { { "Row1-Column1", "Row1-Column2", "Row1-Column3"}, { "Row2-Column1", "Row2-Column2", "Row2-Column3"} }来创建,并使用另一个数组来存储表头名字。类似的数据结构对于基于向量的构建函数也是必须的。
其余的三个构造函数使用JTable特定的数据结构。如果忽略三个参数中的任意一个,则会使用默认的设置。例如,如果我们没有指定TableColumnModel,则会使用默认实现DefaultTableColumnModel,并且会使用TableModel的列顺序来自动填充显示顺序。如果忽略选择模型,则ListSelectionModel会全使用多行选择模型,这就意味着非连续行而不是列可以被选中。
18.2.2 滚动JTable组件
类似于其他的需要更多可用空间的组件,JTable组件实现了Scrollable接口并且应放置在一个JScrollPane中。当JTable对于可用的屏幕状态过大时,滚动条会出现在JScrollPane中,并且列头的名字会出一在每一列的上方。图18-3显示了图18-1中的表没有位于JScrollPane中的显示结果。注意,列头与滚动条都没有出现。这意味着我们不能确定数据的意义,也不能滚动到未显示的行。
所以,我们所创建的每一个表格需要通过类似于下面的代码来将其放置在JScrollPane中:
JTable table = new JTable(...); JScrollPane scrollPane = new JScrollPane(table);
18.2.3 手动放置JTable视图
当位于JScrollPane中的JTable被添加到窗口时,表格会自动显示在表格位置,所以第一行与第一列出现在左上角。如果我们需要将位置调整为原点,我们可以将视窗位置设置回点(0,0)。
为了滚动的目的,依据滚动条的方向,块增长量是视窗的可见宽度与高度。对于水平滚动是100像素,而对于垂直滚动则是单个行的高度。图18-4显示了这些增量的可视化表示。
18.2.4 移除列头
如前所述,将JTable放在JScrollPane中会自动为不同的列名生成列头标签。如果我们不需要列头,我们可以使用多种方法来移除。图18-5显示了一个没有列头的表格的示例。
移除列头最简单的方法就是提供一个空字符串作为列头名。使用前面七个构造函数列表中的第三个JTable构造函数,就会将三个列名替换为""空字符串。
Object rowData[][] = {{"Row1-Column1", "Row1-Column2", "Row1-Column3"}, {"Row2-Column1", "Row2-Column2", "Row2-Column3"}}; Object columnNames[] = { "", "", ""}; JTable table = new JTable(rowData, columnNames); JScrollPane scrollPane = new JScrollPane(table);
因为这种移除列头的方法同时也移除了不同列的描述,也许我们会希望另一种隐藏列头的方法。最简单的方法就是我们告诉JTable我们并不需要表格头:
table.setTableHeader(null);
我们也可以通过继承JTable类并且覆盖受保护的configureEnclosingScrollPane()方法来移除列头,或者是告诉每一个TableColumn其列头值为空。这些是实现相同任务更为复杂的方法。
注意,调用scrollPane.setColumnHeaderView(null)方法并不清除列头。相反,他会使得JScrollPane使用默认的列头。
18.2.5 JTable属性
如表18-1所示,JTable有许多属性,共计40个。这40个属性是对由JComponent,Container与Component类继承所得属性的补充。
注意,行的高度并不是固定的。我们可以使用public void setRowHeight(int row, int rowHeight)方法来修改单个行的高度。
大多数的JTable属性以下三类中的一种:显示设置,选择设置以及自动尺寸调整设置。
显示设置
表18-1中第一个属性子集合允许我们设置各种JTable显示选项。除了由Component继承的foreground与background属性以外,我们可以修改选择前景(selectionForeground)与背景(selectionBackground)颜色。我们可以控制显示哪一个网格线(showGrid)及其颜色(gridColor)。intercellSpacing属性设置处理表格单元之间的额外空间。
选择模式
我们可以使用JTable三种不同的选择模式类型中的一种。我们可以一次选择一行表格元素,一次选择一列表格元素,或是一次选择一个单元格。这三种设置是通过rowSelectionAllowed,columnSelectionAllowed以及cellSelectionEnabled属性来控制的。初始时,仅允许行选择模式。因为默认的ListSelectionModel位于多选模式,我们可以一次选中多行。如果我们不喜欢多选模式,我们可以修改JTable的selectionMode属性,从而使得JTable的行与列选择模式相应的发生变化。当同时允许行选择与列选择时,就会允许单元格选择。
如果我们对JTable的行或是列是否被选中感兴趣,我们可以查询JTable的下列六个属性:selectedColumnCount, selectedColumn, selectedColumns, selectedRowCounts, selectedRow以及selectedRows。ListSelectionModel类为不同的选择模式提供相应的常量。ListSelectionModel接口与DefaultListSelectionModel类已经在第13章中的JList组件信息中进行探讨。他们被用来描述JTable组件中的行与列。他们具有三个设置:
• MULTIPLE_INTERVAL_SELECTION (the default) •SINGLE_INTERVAL_SELECTION •SINGLE_SELECTION
JTable对于行与列具有独立的选择模式。行选择模式被存储在JTable中的selectionModel属性中。列选择模式被存储在TableColumnModel属性。设置JTable的selectionMode属性会为两个独立的JTable选择模式设置选择模式。
一旦设置了选择模式并且用户与组件进行交互,我们可以向选择模型询问发生了什么,或是更确切的,用户选择了什么。表18-2列出了使用DefaultListSelectionModel的可用属性。
如果我们对于了解何时发生选择事件感兴趣,则我们可以向ListSelectionModel注册一个ListSelectionListener。ListSelectionListener在第13中JList组件中进行演示了。
注意,所有的表格索引都是由0开始的。所以第一个可见的列是第0列。
尺寸自动调整模式
JTable属性的最后一个子集处理JTable的列尺寸调整行为。当JTable位于一个尺寸变化的列或是窗口中时,则其如何响应呢?表18-3显示了JTable所支持的五个设置。
列表18-1演示了当调整表格列时每一种设置如何响应。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; public class ResizeTable { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rowData[][] = { {"1", "one", "ichi - \u4E00", "un", "I"}, {"2", "two", "ni -\u4E8C", "deux", "II"}, {"3", "three", "san - \u4E09", "trois", "III"}, {"4", "four", "shi - \u56DB", "quatre", "IV"}, {"5", "five", "go - \u4E94", "cinq", "V"}, {"6", "six", "roku - \u516D", "treiza", "VI"}, {"7", "seven", "shichi - \u4E03", "sept", "VII"}, {"8", "eight", "hachi - \u516B", "huit", "VIII"}, {"9", "nine", "kyu - \u4E5D", "neur", "IX"}, {"10", "ten", "ju - \u5341", "dix", "X"} }; final String columnNames[] = {"#", "English", "Japanese", "French", "Roman"}; Runnable runner = new Runnable() { public void run() { final JTable table= new JTable(rowData, columnNames); JScrollPane scrollPane = new JScrollPane(table); String modes[] = {"Resize All Columns", "Resize Last Column", "Resize Next Column", "Resize Off", "Resize Susequent Columns"}; final int modeKey[] = { JTable.AUTO_RESIZE_ALL_COLUMNS, JTable.AUTO_RESIZE_LAST_COLUMN, JTable.AUTO_RESIZE_NEXT_COLUMN, JTable.AUTO_RESIZE_OFF, JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS }; JComboBox resizeModeComboBox = new JComboBox(modes); int defaultMode = 4; table.setAutoResizeMode(modeKey[defaultMode]); resizeModeComboBox.setSelectedIndex(defaultMode); ItemListener itemListener = new ItemListener() { public void itemStateChanged(ItemEvent e) { JComboBox source = (JComboBox)e.getSource(); int index = source.getSelectedIndex(); table.setAutoResizeMode(modeKey[index]); } }; resizeModeComboBox.addItemListener(itemListener); JFrame frame = new JFrame("Resizing Table"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(resizeModeComboBox, BorderLayout.NORTH); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
图18-6显示了程序初始时的显示。变化JComboBox,从而我们就可以修改列的尺寸调整行为。
18.2.6 渲染表格单元
默认情况下,表格数据的渲染是通过JLabel完成的。存储在表格中的值被作为文本字符串进行渲染。同时也为Date与Number子类等类安装了的额外的默认渲染器,但是他们并没有被激活。我们将会在本章稍后的章节中了解如何激活这些渲染器。
使用TableCellRenderer接口与DefaultTableCellRenderer类
TableCellRenderer接口定义了一个唯一的方法。
public interface TableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column); }
通过使用指定给getTableCellRendererComponent()方法的信息,则会创建合适的渲染器组件并且使用其特定的方法来显示JTable的相应内容。“合适”意味着反映我们决定显示的表格单元状态的渲染器,例如当我们需要区别显示选中的表格单元与未选中的表格单元,或者是当表格单元获得输入焦点时,我们希望选中的单元如何显示等。
要查看一个简单的演示,如图18-7所示,其中依据渲染器所在的行显示了不同的颜色。
用于生成图18-7示例的自定义渲染器的代码显示在列表18-2中。
package swingstudy.ch18; import java.awt.Color; import java.awt.Component; import javax.swing.JTable; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; public class EvenOddRenderer implements TableCellRenderer { public static final DefaultTableCellRenderer DEFAULT_RENDERER = new DefaultTableCellRenderer(); @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { // TODO Auto-generated method stub Component renderer = DEFAULT_RENDERER.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); Color foreground, background; if(isSelected) { foreground = Color.YELLOW; background = Color.GREEN; } else { if(row%2==0) { foreground = Color.BLUE; background = Color.WHITE; } else { foreground = Color.WHITE; background = Color.BLUE; } } renderer.setForeground(foreground); renderer.setBackground(background); return renderer; } }
表格的渲染器可以为单个类或是特定的列而安装。要将渲染器安装为JTable的默认渲染器,换句话说,对于Object.class,使用类似下面的代码:
TableCellRenderer renderer = new EvenOddRenderer(); table.setDefaultRenderer(Object.class, renderer);
一旦安装,EvenOddRenderer将会用于其类不具有特定渲染器的任意列。TableModel的public Class getColumnClass()方法负责返回用作特定列中所有表格单元渲染器的类。DefaultTableModel为所有表格单元返回Object.class;所以,EvenOddRenderer将会用于所有的表格单元。
使用EvenOddRenderer来生成图18-7示例的示例程序显示在列表18-3中。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.EventQueue; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.TableCellRenderer; public class RendererSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rows[][] = { {"one", "ichi - \u4E00"}, {"two", "ni - \u4E8C"}, {"three", "san - \u4E09"}, {"four", "shi - \u56DB"}, {"fiv", "go - \u4E94"}, {"six", "roku - \u516D"}, {"seven", "shichi - \u4E03"}, {"eight", "hachi - \u516B"}, {"nine", "kyu - \u4E5D"}, {"ten", "ju - \u5341"} }; final Object headers[] = {"English", "Japanese"}; Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Renderer Sample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JTable table = new JTable(rows, headers); TableCellRenderer renderer = new EvenOddRenderer(); table.setDefaultRenderer(Object.class, renderer); JScrollPane scrollPane = new JScrollPane(table); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
使用工具提示
默认情况下,我们的表格单元将会显示我们配置其显示的工具提示文本。与JTree组件不同,我们并不需要手动向表格注册ToolTipManager。然而,如果我们的表格不显示工具提示文本,如果我们使用类似下面的代码来取消ToolTipManager的注册,表格的响应就会更为迅速:
// Explicitly ToolTipManager.sharedInstance().unregisterComponent(aTable); // Implicitly yourTable.setToolTipText(null);
18.2.7 处理JTable事件
并没有我们可以直接注册到JTable的JTable事件。要确定某件事情何时发生,我们必须注册到JTable的模型类:TableModel,TableColumnModel或是ListSelectionModel。
18.2.8 自定义JTable观感
每一个可安装的Swing观感都提供了不同的JTable外观与默认的UIResource值设置集合。图18-8显示了预安装的观感类型Motif,Windows与Ocean的JTable组件外观。在图所示的三个观感中,第三行是高亮显示的,而第一列的颜色显示正在编辑状态。
JTable可用的UIResource相关的属性集合显示在表18-4中。JTable组件有21个不同的属性。