转载:http://www.tuicool.com/articles/qE7FNv
在JTable中自定义单元格渲染器是非常简单和容易的。对于单元格渲染器来说,它的主要任务就是为目标单元格返回一个Component对象,以呈现其内容。说到Component,就应该豁然开朗了,因为在Swing中,所有可在屏幕上显示的图形控件都是Component的直接或间接子类,也就是说,理论上你可以在JTable单元格中显示任意图形控件!
当然,实际上有一些控件是不能在单元格中显示的,比如JFrame这样的独立窗口控件,为了避免循环引用也不允许将当前表格作为渲染器,但即便是这样,我们的可用选择仍然多的数不清。我们只需要继承任意可用的Component子类,同时实现TableCellRenderer接口,就可以制作一个自己的单元格渲染器。作为Swing自带的默认单元格渲染器,DefaultTableCellRenderer的使用频率非常高,该类继承自JLabel,是最简单的单元格渲染器实现。参考这一实现,我们可以尝试分别继承自JComboBox、JButton、JCheckBox、JProgressBar等,实现在表格单元格中显示下拉框、按钮、复选框和进度条等稍微高级的UI控件。
值得注意的是,单元格的UI控件没有拦截事件的能力,也就是说,他们仅仅做数据呈现工作,不能够与用户交互(因为事件被JTable在上层拦截并处理),这也意味着某些如下拉框、复选框等可交互的UI控件“只能看不能用”。不过我们可以通过监听JTable的鼠标事件并通过接口转换到对应的单元格和借助单元格编辑器的方式分别解决上述两个问题。
如下代码,演示了如何通过自定义单元格渲染器实现在表格中显示超链接并在链接被点击时自动打开对应页面的功能:
package cn.ysh.studio.jtable; import java.awt.*; import java.awt.event.MouseEvent; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.event.MouseInputListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; /** * 显示超链接的JTable单元格渲染器 * @author 杨胜寒 * @date 2013-06-20 16:08:36 */ public class LinkCellRenderer extends DefaultTableCellRenderer implements MouseInputListener { //鼠标事件所在的行 private int row = -1; //鼠标事件所在的列 private int col = -1; //当前监听的Table private JTable table = null; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { //恢复默认状态 this.table = table; super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); this.setForeground(Color.black); table.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); this.setText(value.toString()); //如果当前需要渲染器的单元格就是鼠标事件所在的单元格 if (row == this.row && column == this.col) { //如果是第二列(第二列是显示超链接的列) if (column == 1) { //改变前景色(文字颜色) this.setForeground(Color.blue); //改变鼠标形状 table.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); //显示超链接样式 this.setText("<html><u>" + value.toString() + "</u></html>"); } setBackground(table.getSelectionBackground()); } else if (isSelected) { //如果单元格被选中,则改变前景色和背景色 setForeground(table.getSelectionForeground()); setBackground(table.getSelectionBackground()); } else { //其他情况下恢复默认背景色 setBackground(Color.white); } return this; } /** * 鼠标移出事件 * @param e */ public void mouseExited(MouseEvent e) { if (table != null) { int oldRow = row; int oldCol = col; //鼠标移出目标表格后,恢复行列数据到默认值 row = -1; col = -1; //当之前的行列数据有效时重画相关区域 if (oldRow != -1 && oldCol != -1) { Rectangle rect = table.getCellRect(oldRow, oldCol, false); table.repaint(rect); } } } /** * 鼠标拖动事件 * @param e */ public void mouseDragged(MouseEvent e) { mouseMoved(e); } /** * 鼠标移动事件 * @param e */ public void mouseMoved(MouseEvent e) { if (table != null) { Point p = e.getPoint(); int oldRow = row; int oldCol = col; row = table.rowAtPoint(p); col = table.columnAtPoint(p); //重画原来的区域 if (oldRow != -1 && oldCol != -1) { Rectangle rect = table.getCellRect(oldRow, oldCol, false); table.repaint(rect); } //重画新的区域 if (row != -1 && col != -1) { Rectangle rect = table.getCellRect(row, col, false); table.repaint(rect); } } } /** * 鼠标单击事件 * @param e */ public void mouseClicked(MouseEvent e) { //获取事件所在的行列坐标信息 Point p = e.getPoint(); int c = table.columnAtPoint(p); if(c != 1){ return; } int r = table.rowAtPoint(p); try { //取得目标单元格的值,即链接信息 URL url = new URL(table.getValueAt(r, c).toString()); //在系统默认浏览器中打开链接 Desktop.getDesktop().browse(url.toURI()); } catch (Exception ex) { Logger.getLogger(LinkCellRenderer.class.getName()).log(Level.SEVERE, null, ex); } } /** * 鼠标按下事件 * @param e */ public void mousePressed(MouseEvent e) { } /** * 鼠标释放事件 * @param e */ public void mouseReleased(MouseEvent e) { } /** * 鼠标进入事件 * @param e */ public void mouseEntered(MouseEvent e) { } /** * 测试方法 * @param args */ public static void main(String[] args) { //将在表格中呈现的数据 Object[] header = new String[]{"标题", "链接"}; Object[][] data = new String[10][2]; for (int i = 0; i < 10; i++) { data[i][0] = "网页标题"; data[i][1] = "http://www.yshjava.cn/post/529.html"; } //构建表格数据模型 TableModel model = new DefaultTableModel(data, header); //创建表格对象 JTable table = new JTable(model); //创建单元格渲染器暨鼠标事件监听器 LinkCellRenderer renderer = new LinkCellRenderer(); //注入渲染器 table.setDefaultRenderer(Object.class, renderer); //注入监听器 table.addMouseListener(renderer); table.addMouseMotionListener(renderer); //为表格增加爱滚动窗格 JScrollPane sp = new JScrollPane(table); //创建窗口程序 JFrame f = new JFrame("JTable 单元格超链接测试"); f.getContentPane().add(sp, BorderLayout.CENTER); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(800, 600); f.setLocationRelativeTo(null); //显示窗口 f.setVisible(true); } }
效果图如下所示:
在上面的例子中,仅仅演示了如何通过监听JTable的鼠标事件来影响对应单元格的行为和外观,但是并未涉及到如何解决交互型UI在JTable单元格中不可用的问题。解决这个问题,需要借助TableCellEditor单元格编辑器的配合来实现,简单来说,展现靠渲染器,交互操作靠编辑器。
TableCellEditor跟TableCellRenderer的原理几乎是一样的,都是接口,Swing都提供了简单的默认实现,都负责生成UI控件来展现或供编辑单元格的数据,不同的是TableCellEditor有接收交互事件的能力,而TableCellRenderer没有,因此可以简单认为“编辑器是动态的,负责编辑,而渲染器是静态的,负责展现”。单元格编辑器不是本文的主题,不做过多说明。放一张NetBeans可视化UI设计器的一个窗口截图,图中的表格就是通过自定义渲染器和编辑器实现的。