最近在為學校做一個工資發放軟件,要用JAVA SWING制作相應的工資表,這就涉及到多行表頭及表格的合並。我足足花了3天的時間去找相關的資料,然而基本上都是E文的,而且所以例子的代碼都沒有注解,所以我決定將我所收集的資料整理公布出來,希望能給大家一些幫助。由於本人只是一名小學教師,水平有限,如果有什麼不正確的地方,請多包涵。
廢話少說,轉入正題吧!
一、單元格合並。
Jtable沒有提供現成的合並單元格的方法,但是使用其所提供的方法仍然能做到這一點,只是復雜了一些。為了合並單元格,我們可以使用Jtable的三個方法:getCellRect(),columnAtPoint(),and rowAtPoint()。第一個方法返回一個單元格的邊界(Rectangle類),第二、三個方法分別返回屏幕指定位置的列和行。為了實現單元格合並,我們需要重載(overwrite)這三個方法。
另外,網上的資料提到,大部分的swing components 並不是直接由paint()方法來渲染(render),而是使用ComponentUI對象來完成渲染的。所以我們需要找出渲染Jtable的ComponentUI對象,並且修改它以達到我們的目的。
由於要實現多行多列單元格合並需要多個類相互協作,直接寫出來的話可能比較復雜,所以我先講一下跨列的單元格合並的方法,然後再提供一個完整的例子。
由於swing裡沒有可記錄單元格合並情況的數據模型,所以我們需要一個新的類,它要包涵一個方法來取得單元格的所跨越的列數。另外,為了使用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的目的是打破單元格和表格的直接依賴關系,並且防止當一個單元格被修改時重畫整個表。