18.5 JTableHeader类
每一个JTableHeader实例表示所有不同列的头集合中的一个。JTableHeader对象集合放置在JScrollPane中列头视图中。
我们很少需要直接使用JTableHeader。然而我们可以配置列头的某些特征。
18.5.1 创建JTableHeader
JTableHeader有两个属性。一个使用默认的TableColumnModel,而另一个需要显式指定模型。
public JTableHeader() JComponent headerComponent = new JTableHeader() public JTableHeader(TableColumnModel columnModel) JComponent headerComponent = new JTableHeader(aColumnModel)
18.5.2 JTableHeader属性
如表18-11所示,JTableHeader有十个不同的属性。这些属性可以使得我们配置用户可以通过特定的列头做什么或是列头如何显示。
18.5.3 在表格头中使用工具提示
默认情况下,如果我们为表格头设置工具提示,所有的列头都会共享相同的工具提示文本。要为特定的列指定工具提示文本,我们需要创建或是获取渲染器,然后为渲染器设置工具提示。对于单个的单元也是如些。图18-12显示了这种定制结果显示的样子。
图18-12中定制的源码显示在列表18-13中。除非我们在前面设置了列头,并没有必要首先检测特定列的头是null。
JLabel headerRenderer = new DefaultTableCellRenderer(); String columnName = table.getModel().getColumnName(0); headerRenderer.setText(columnName); headerRenderer.setToolTipText("Wave"); TableColumnModel columnModel = table.getColumnModel(); TableColumn englishColumn = columnModel.getColumn(0); englishColumn.setHeaderRenderer((TableCellRenderer)headerRenderer);
18.5.4 自定义JTableHeader观感
JTableHeaderUIResource相关属性的可用集合显示在表18-12中。五个属性用于控制头渲染器的颜色,字体与边框。
18.6 编辑表格单元
编辑JTable单元与编辑JTree单元基本上是相同的。事实上,默认的表格单元编辑器,DefaultCellEditor,同时实现了TableCellEditor与TreeCellEditor接口,从而使得我们可以为表格与树使用相同的编辑器。
点击可编辑器的单元将会使得单元处理框架模式。(所需要的点击次数依赖于编辑器的类型。)所有单元的默认编辑器是JTextField。尽管这对于许多数据类型可以工作得很好,但是对于其他的许多数据类型却并不合适。所以,我们或者不支持非文本信息的编辑或者是为我们的JTable设置特殊的编辑器。对于JTable,我们可以为一个特定的类类型或是列注册一个编辑器。然后,当表格在多个相应类型的单元上运行时,则会使用所需要的编辑器。
注意,当没有安装特殊的编辑器时,则会使用JTextField,尽管他对于内容并不合适。
18.6.1 TableCellEditor接口与DefaultCellEditor类
TableCellEditor接口定义了JTable获取编辑器所必须的方法。TableCellEditor的参数列表与TableCellRenderer相同,所不同的是hasFocused参数。因为单元正在被编辑,已知他已经具有输入焦点。
public interface TableCellEditor extends CellEditor { public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column); }
正如第17章所描述的,DefaultCellEditor提供了接口的实现。他提供了JTextField作为一个编辑器,JCheckBox作为另一个编辑器,而JComboBox作为第三个编辑器。
如表格18-13所示,在大多数情况下,默认编辑器为JTextField。如果单元数据可以由一个字符串转换而成或是转换成一个字符串,类提供了一个具有String参数的构造函数,编辑器提供了数据的文本表示作为初始编辑值。然后我们可以编辑内容。
18.6.2 创建一个简单的单元编辑器
作为修改JTable中非String单元的简单示例,我们可以为用户提供一个固定的颜色选择集合。然后当用户选择颜色时,我们可以向表格模型返回相应的Color值。DefaultCellEditor为这种情况提供了一个JComboBox。在配置JComboBox的ListCellRenderer正确的显示颜色之后,我们就会有一个TableCellEditor用于选择颜色。图18-13显示了可能显示结果。
提示,任何时候我们都可以重新定义所有的选项,我们可以通过DefaultCellEditor将JComboBox作为我们的编辑器。
列表18-14显示了表示了图18-13中所示的用于Color列事件的TableCellRenderer类以及JComboBox TableCellEditor的ListCellRenderer。由于这两个渲染器组件的相似性,他们的定义被组合在一个类中。
package swingstudy.ch18;
import java.awt.Color;
import java.awt.Component;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import swingstudy.ch04.DiamondIcon;
public class ComboTableCellRenderer implements ListCellRenderer,
TableCellRenderer {
DefaultListCellRenderer listRenderer = new DefaultListCellRenderer();
DefaultTableCellRenderer tableRenderer = new DefaultTableCellRenderer();
public void configureRenderer(JLabel renderer, Object value) {
if((value != null) && (value instanceof Color)) {
renderer.setIcon(new DiamondIcon((Color)value));
renderer.setText("");
}
else {
renderer.setIcon(null);
renderer.setText((String)value);
}
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
// TODO Auto-generated method stub
tableRenderer = (DefaultTableCellRenderer)tableRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
configureRenderer(tableRenderer, value);
return tableRenderer;
}
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
// TODO Auto-generated method stub
listRenderer = (DefaultListCellRenderer)listRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
configureRenderer(listRenderer, value);
return listRenderer;
}
}
为了演示新的组合渲染器的使用以及显示一个简单的表格单元编辑器,显示在列表18-15中的程序创建了一个数据模型,其中一个列为Color。在两次安装渲染器并且设置表格单元编辑器以后,就可以显示表格并且Color可以被编辑。
package swingstudy.ch18;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
public class EditableColorColumn {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Runnable runner = new Runnable() {
public void run() {
Color choices[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA};
ComboTableCellRenderer renderer = new ComboTableCellRenderer();
JComboBox comboBox = new JComboBox(choices);
comboBox.setRenderer(renderer);
TableCellEditor editor = new DefaultCellEditor(comboBox);
JFrame frame = new JFrame("Editable Color Table");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TableModel model = new ColorTableModel();
JTable table = new JTable(model);
TableColumn column = table.getColumnModel().getColumn(3);
column.setCellRenderer(renderer);
column.setCellEditor(editor);
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(400, 150);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
列表18-16显示在这个示例以及下个示例中所用的表格模型。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.Color; import java.awt.EventQueue; import javax.swing.DefaultCellEditor; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.table.TableCellEditor; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; public class EditableColorColumn { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { Color choices[] = {Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA}; ComboTableCellRenderer renderer = new ComboTableCellRenderer(); JComboBox comboBox = new JComboBox(choices); comboBox.setRenderer(renderer); TableCellEditor editor = new DefaultCellEditor(comboBox); JFrame frame = new JFrame("Editable Color Table"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TableModel model = new ColorTableModel(); JTable table = new JTable(model); TableColumn column = table.getColumnModel().getColumn(3); column.setCellRenderer(renderer); column.setCellEditor(editor); JScrollPane scrollPane = new JScrollPane(table); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(400, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
18.6.3 创建复杂的单元编辑器
尽管前面的例子演示了如何以列表框TableCellEditor的方向用户提供一个确定的选项集合,但是提供一个JColorChooser作为选项似乎是更好的选择(至少,在颜色选择中是如此)。当定义我们自己的TableCellEditor时,我们必须实现单一的TableCellEditor方法来获得相应的组件。我们必须同时实现CellEditor的七个方法,因为他们管理并通知一个CellEditorListener对象列表,同时控制一个单元何时可以编辑。以一个AbstractCellEditor子类作为起点会使得定义我们自己的TableCellEditor更为简单。
通过扩展AbstractCellEditor类,只有CellEditor方法中的getCellEditorValue()方法需要为编辑器进行自定义。实现上述步骤并且提供了一个JButton,当点击整个编辑器组件时弹出JColorChooser。列表18-17显示了自定义编辑器的源码。
package swingstudy.ch18; import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.AbstractCellEditor; import javax.swing.JButton; import javax.swing.JColorChooser; import javax.swing.JTable; import javax.swing.table.TableCellEditor; import swingstudy.ch04.DiamondIcon; public class ColorChooserEditor extends AbstractCellEditor implements TableCellEditor { private JButton delegate = new JButton(); Color savedColor; public ColorChooserEditor() { ActionListener actionListener = new ActionListener() { public void actionPerformed(ActionEvent event) { Color color = JColorChooser.showDialog(delegate, "Color Chooser", savedColor); ColorChooserEditor.this.changeColor(color); } }; delegate.addActionListener(actionListener); } @Override public Object getCellEditorValue() { // TODO Auto-generated method stub return savedColor; } public void changeColor(Color color) { if(color != null) { savedColor = color; delegate.setIcon(new DiamondIcon(color)); } } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { // TODO Auto-generated method stub changeColor((Color)value); return delegate; } }
图18-14显示了ColorChooserEditor的运行结果。
使用新的ColorChooserEditor的示例程序显示在列表18-18中。示例程序重用了前面显示在列表18-16中的ColorTableModel数据模型。设置ColorChooserEditor简单的涉及为相应的列设置TableCellEditor。
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.TableCellEditor; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; public class ChooserTableSample { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Editable Color Table"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TableModel model = new ColorTableModel(); JTable table = new JTable(model); TableColumn column = table.getColumnModel().getColumn(3); ComboTableCellRenderer renderer = new ComboTableCellRenderer(); column.setCellRenderer(renderer); TableCellEditor editor = new ColorChooserEditor(); column.setCellEditor(editor); JScrollPane scrollPane = new JScrollPane(table); frame.add(scrollPane, BorderLayout.CENTER); frame.setSize(400, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
18.7 打印表格
JDK5.0的一个新特性也是最容易使用一个特性:打印表格功能。通过简单的JTable的public boolean print() throws PrinterException方法,我们就可以在打印机在多面上打印一个大表格。甚至是如果我们不喜欢将表格适应整页纸的宽度的默认行为,我们可以在多个页面上扩展列。
为了演示这种行为,列表18-19使用基本的JTable示例代码来生成图18-1,向表格添加更多的行,并且添加一个打印按钮。
package swingstudy.ch18; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.print.PrinterException; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; public class TablePrint { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub final Object rows[][] = { {"1", "ichi - \u4E00"}, {"2", "ni -\u4E8C"}, {"3", "san - \u4E09"}, {"4", "shi - \u56DB"}, {"5", "go - \u4E94"}, {"6", "roku - \u516D"}, {"7", "shichi - \u4E03"}, {"8", "hachi - \u516B"}, {"9", "kyu - \u4E5D"}, {"10","ju - \u5341"}, {"1", "ichi - \u4E00"}, {"2", "ni -\u4E8C"}, {"3", "san - \u4E09"}, {"4", "shi - \u56DB"}, {"5", "go - \u4E94"}, {"6", "roku - \u516D"}, {"7", "shichi - \u4E03"}, {"8", "hachi - \u516B"}, {"9", "kyu - \u4E5D"}, {"10","ju - \u5341"}, {"1", "ichi - \u4E00"}, {"2", "ni -\u4E8C"}, {"3", "san - \u4E09"}, {"4", "shi - \u56DB"}, {"5", "go - \u4E94"}, {"6", "roku - \u516D"}, {"7", "shichi - \u4E03"}, {"8", "hachi - \u516B"}, {"9", "kyu - \u4E5D"}, {"10","ju - \u5341"} }; final Object headers[] = {"English", "Japanese"}; Runnable runner = new Runnable() { public void run() { JFrame frame = new JFrame("Table Printing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JTable table = new JTable(rows, headers); JScrollPane scrollPane = new JScrollPane(table); frame.add(scrollPane, BorderLayout.CENTER); JButton button = new JButton("Print"); ActionListener printAction = new ActionListener() { public void actionPerformed(ActionEvent event) { try { table.print(); } catch(PrinterException pe) { System.out.println("Error printing: "+pe.getMessage()); } } }; button.addActionListener(printAction); frame.add(button, BorderLayout.SOUTH); frame.setSize(300, 150); frame.setVisible(true); } }; EventQueue.invokeLater(runner); } }
在点击打印Print按钮之后,会向用户提示一个经典的打印机选择对话框,如图18-15所示。
在用户点击打印对话框中的打印按钮之后,打印开始。会显示一个类似于图18-16中所示的对话框。
确实,很容易使用JDK 5.0打印多页表格。print()方法会返回一个boolean值,从而我们可以发现用户是否关闭了操作。
对于查找更多打印操作控制的用户,JTable具有多个重载的print()方法版本。类似于简单的print()方法,他们都抛出PrinterException。
其中一个print()版本会允许我们指定打印模式:
public boolean print(JTable.PrintModel printMode)
JTable.PrintModel参数是一个FIT_WIDTH与NORMAL的枚举。当使用无参数的print()版本没有指定时,则默认为FIT_WIDTH。
另一个版本的方法允许我们指定页眉与页脚:
public boolean print(JTable.PrintMode printMode, MessageFormat headerFormat, MessageFormat footerFormat
MessageFormat来自于java.text包。页眉与页脚格式化字符串的一个参数是页数。要显示页数,在我们的格式化字符串我们希望显示页数的地方包含{0}。两个都会显示在页面的中间,而页眉使用稍大一些的字符。为了演示,将列表18-19中的print()方法调用改为下面的形式:
MessageFormat headerFormat = new MessageFormat("Page {0}"); MessageFormat footerFormat = new MessageFormat("- {0} -"); table.print(JTable.PrintMode.FIT_WIDTH, headerFormat, footerFormat);
最后一个print()版本是一个综合版本,使得我们在不显示打印机对话框的情况配置默认打印机所需要的属性,例如要打印多少份。
public boolean print(JTable.PrintMode printMode, MessageFormat headerFormat, MessageFormat footerFormat, boolean showPrintDialog, PrintRequestAttributeSet attr, boolean interactive)
对于我们不希望用户与打印机交互的情况下,可以考虑使用最后一个版本。
18.8 小结
在本章中我们探讨了JTable组件的内部细节。我们了解了如何为JTable自定义TableModel,TableColumnModel与ListSelectionModel。我们深入了不同的表格模型的抽象与具体实现。另外,我们探讨了各种表格模型的内部元素,例如TableColumn与JTableHeader类。我们同时了解了如何通过提供一个自定义的TableCellRenderer与TableCellEditor来自定义JTable的显示与编辑。最后,我们了解了通过print()方法打印表格。
在第19章中,我们将会探讨JFC/Swing组件集合的拖拽体系结构。