一、单元格合并。
(1)我们可以使用Jtable的三个方法:getCellRect(),columnAtPoint(),and rowAtPoint()。第一个方法返回一个单元格的边界(Rectangle类),第二、三个方法分别返回屏幕指定位置的列和行。为了实现单元格合并,我们需要重载(overwrite)这三个方法。
(2)另外我们需要找出渲染Jtable的ComponentUI对象,并且修改它以达到我们的目的。
(3)创建新的类记录单元格合并情况的数据模型,它要包涵一个方法来取得单元格的所跨越的列数。另外,为了使用Jtable画(paint)起来更容易些,我们需要一个方法来确定指定单元格是否被其它单元格所覆盖,被哪个单元格覆盖。我们将这两种方法都集成在接口Cmap里:
代码: |
package com.neuri.ctable; public interface CMap { /** * @参数row:指定单元格所在的逻辑行 * @参数column:指定单元格所在的逻辑列 * @返回指定单元格所跨越的列数 */ int span (int row, int column); /** * @参数row:指定单元格所在的逻辑行 * @参数column:指定单元格所在的逻辑列 * @返回覆盖指定单元格的可视单元格的列值,如果单元格本来就是可视的话,返回自身的列值 */ int visibleCell(int row, int column); } |
现在我们开始重载上面提及过的三个方法。由于我们目前只关注于跨列单元格的合并,方法rowAtPoint()就不用重载了。然而,方法columnAtPoint()就必须重载了,我们会使用Jtable自身的方法来取得指定单元格的列值,并且计算出覆盖该单元格的可视单元格列值(如果该单元格本来就是可视的,则返回自身列值)。在单元格合并后,在合并区域内只有一个跨越多列的可视单元格,其它被覆盖的单元格则不会再被渲染。当使用getCellRect()方法取得被覆盖的单元格的大小时,都返回覆盖该单元格的可视单元格的大小。
代码: |
package com.neuri.ctable; import javax.swing.*; import javax.swing.table.*; import java.awt.*; public class CTable extends JTable { public CMap map; public CTable(CMap cmp, TableModel tbl) { super(tbl); map=cmp; setUI(new CTUI());//设置Jtable的渲染UI } public Rectangle getCellRect(int row, int column, boolean includeSpacing){ // 该方法是Jtable构建时所必须的 if (map==null) return super.getCellRect(row,column, includeSpacing); // 指定单元格的可视单元格列值 int sk=map.visibleCell(row,column); Rectangle r1=super.getCellRect(row,sk,includeSpacing); // 如果指定单元格列宽不为1,累计出跨列单元格的宽度 if (map.span(row,sk)!=1) for (int i=1; i<map.span(row,sk); i++){ r1.width+=getColumnModel().getColumn(sk+i).getWidth(); } return r1; } public int columnAtPoint(Point p) { int x=super.columnAtPoint(p); // 当指定位置不在Table内时,返回-1 if (x<0) return x; int y=super.rowAtPoint(p); //获取指定位置可视单元格的列值 return map.visibleCell(y,x); } } |
现在剩下的就只有创建一个表格的渲染对象了。不同的用户接口管理器(user interface managers)使用不同的类来画表格。我们会继承子类 javax.swing.plaf.basic.BasicTableUI,并且重载其方法 paintComponent。
在组件(component)画在屏幕上之前,它已经被初始化和设定好了,所以我们能使用其内部的属性 table 和 rendererPane。属性 table 就是将要被画在屏幕的表格,rendererPane 是用于将单元格画在表格中的特殊对象。使用RendererPane的目的是打破单元格和表格的直接依赖关系,并且防止当一个单元格被修改时重画整个表。
BasicTableUI的方法getClipBounds是用于找出表格的哪一部分将会被画出来,所以我们首先要知道那些行是可视的,我们可以使用Jtable 的rowAtPoint方法。我们可以使用Rectangle类的intersects方法来确定这些行中的所有单元格是否将会被画在屏幕上。在我们画任何一个单元格前,我们必须检查一下当前单元格是否可视,如果该单元格是被其它单元格所覆盖的,就将覆盖它的单元格画出来。
根据单元格是否正在被编辑,单元格将会被方法getCellEditor或getCellRenderer所返回的对象画出来。如果你查看一下BasicTableUI的源代码,你就会发现所以单元格会先被BasicTableUI调用table.prepareRenderer画(drawn)出来,然后再被BasicTableUI调用rendererPane.paintComponent来渲染(paint)。我们会采用同样的方法。
代码: |
package com.neuri.ctable; import javax.swing.table.*; import javax.swing.plaf.basic.*; import java.awt.*; import javax.swing.*; public class CTUI extends BasicTableUI { public void paint(Graphics g, JComponent c) { Rectangle r=g.getClipBounds(); int firstRow=table.rowAtPoint(new Point(0,r.y)); int lastRow=table.rowAtPoint(new Point(0,r.y+r.height)); // -1 is a flag that the ending point is outside the table if (lastRow<0) lastRow=table.getRowCount()-1; for (int i=firstRow; i<=lastRow; i++) paintRow(i,g); } private void paintRow(int row, Graphics g) { Rectangle r=g.getClipBounds(); for (int i=0; i<table.getColumnCount();i++) { Rectangle r1=table.getCellRect(row,i,true); if (r1.intersects(r)) // at least a part is visible { int sk=((CTable)table).map.visibleCell(red,i); paintCell(row,sk,g,r1); // increment the column counter i+=((CTable)table).map.span(row,sk)-1; } } } private void paintCell(int row, int column, Graphics g,Rectangle area) { int verticalMargin = table.getRowMargin(); int horizontalMargin = table.getColumnModel().getColumnMargin(); Color c = g.getColor(); g.setColor(table.getGridColor()); g.drawRect(area.x,area.y,area.width-1,area.height-1); g.setColor(c); area.setBounds(area.x + horizontalMargin/2, area.y + verticalMargin/2, area.width - horizontalMargin, area.height - verticalMargin); if (table.isEditing() && table.getEditingRow()==row && table.getEditingColumn()==column) { Component component = table.getEditorComponent(); component.setBounds(area); component.validate(); } else { TableCellRenderer renderer = table.getCellRenderer(row, column); Component component = table.prepareRenderer(renderer, row, column); if (component.getParent() == null) rendererPane.add(component); rendererPane.paintComponent(g, component, table, area.x, area.y, area.width, area.height, true); } } } |
{
super.setDataVector(newData, columnNames);
cellAtt = new DefaultCellAttribute(dataVector.size(),columnIdentifiers.size());
}
有心重整tame,先记一笔。