表格(單元格合并)
JTable的單個單元格最復雜的操作也就是Renderer渲染和Editor編輯,然后增加事件處理和懸浮框提示,最多再加點特殊顯示效果,在前面的例子里都已經講過了,這里就剩下最后一個關于單元格的操作了,單元格的合并和拆分.
JTable的單元格可編輯時可以把它看做一個JTextField,不可操作時可以看做一個JLabel,對于單元格的合并和拆分操作來說就是把JLabel或JTextField進行合并和拆分的過程.JTable單元格的合并簡單來說就是把你選定的要合并的單元格的邊線擦掉,然后調整寬度和高度,再在這幾個合并的單元格外圍畫一個新的邊線,然后設置JTable的UI,刷新就可以了.
先看完成后的界面:

然后是工程的目錄結構:

其中CustomCell就合并后的Cell,它儲存了當前那幾個Cell被合并,以及合并后的寬和高用于計算.CustomCellRenderer就是合并后的Cell的Renderer了,它用于合并后Cell內容的顯示,CustomCellUI則是合并Cell的UI了,這是主要的合并Cell用到的類,剩下的兩個接口ICellAttribute和ICellSpan則是合并Cell的接口,里面定義Cell增刪和更新的方法,而SpanCellTablePanel類是因為做成的合并單元格JTable很難使用,提供的輔助類,傳入數據則把JTable創建出來,并根據數據實現了合并單元格.
先看接口IcellSpan,它提供設置單元格是否可見,單元格的合并和拆分方法,單元格拆分的Index方法需要實現類完成.
/**
*theinterfacethataboutcellspan.
*/
publicinterface ICellSpan {
方法如下:
/**
*getcellSpan.
*/
publicint[] getSpan(int row, int column);
/**
*setcellspan.
*/
publicvoid setSpan(int[] span, int row, int column);
這兩個方法是提供JTable真正的行和列和合并后的行和列的對應關系.
/**
*iscellvisible.
*/
publicboolean isVisible(int row, int column);
/**
*wherecombine.
*/
publicvoid combine(int[] rows, int[] columns);
/**
*wheresplit.
*/
publicvoid split(int row, int column);
提供當前Jtable那幾個行和列合并;以及合并后的某一行和列的拆分.
然后是IcellAttribute接口,它提供增加行、增加列、以及插入行、取得和設置大小(不是物理大小,是相對于JTable的大小)的方法:
/**
*theinterfacethataboutcellattribute.
*/
publicinterface ICellAttribute {
再看方法:
/**
*addcolumntocell.
*/
publicvoid addColumn();
/**
*addrowtocell.
*/
publicvoid addRow();
/**
*insertrowtocell
*/
publicvoid insertRow(int row);
這三個方法提供行和列的增加操作
/**
*getcellsize.
*/
public Dimension getSize();
/**
*setcellsize.
*/
publicvoid setSize(Dimension size);
這兩個方法提供單元格大小的設置和取得.
再看實現這兩個接口的類CustomCell,它是合并后的單元格的信息保存類:
/**
*setcellAttributespanandsoon.
*/
publicclass CustomCell implements ICellAttribute, ICellSpan {
有三個屬性:
/**cellwidth.*/
privateintrowSize = 0;
/**cellheight.*/
privateintcolumnSize = 0;
/**cellspans.*/
privateint[][][] span = null;
分別保存合并后單元格的寬度和高度(這個寬和高的意思是在兩個方向上有幾個JTable的單元格大小),以及合并的單元格是由原本JTable的那幾個行和列組成的.
先是構造函數:
/**
*setcellattribute.
*/
public CustomCell() {
this(1, 1);
}
setSize(new Dimension(columnSize, rowSize));
在setSize方法里,初始化屬性:
@Override
publicvoid setSize(Dimension size) {
columnSize = size.width;
rowSize = size.height;
span = newint[rowSize][columnSize][2];
initValue();
}
然后是初始化方法,
/**
*setcellinit.
*/
privatevoid initValue() {
for (int i = 0; i < span.length; i++) {
for (int j = 0; j < span[i].length; j++) {
span[i][j][ICellSpan.COLUMN] = 1;
span[i][j][ICellSpan.ROW] = 1;
}
}
}
然后就是實現接口的方法,
@Override
publicint[] getSpan(int row, int column) {
返回span數組對應的行和列:
returnspan[row][column];
@Override
publicvoid setSpan(int[] span, int row, int column) {
設置span數組對應的行和列:
this.span[row][column] = span;
@Override
publicvoid addColumn() {
@Override
publicvoid addRow() {
@Override
publicvoid insertRow(int row) {
這三個方法很類似,都是先取得舊有的span:
int[][][] oldSpan = span;
int numRows = oldSpan.length;
int numColumns = oldSpan[0].length;
然后創建新的:
span = newint[numRows + 1][numColumns][2];
System.arraycopy(oldSpan, 0, span, 0, numRows);
最后賦予新的值:
for (int i = 0; i < numColumns; i++) {
span[numRows][i][ICellSpan.COLUMN] = 1;
span[numRows][i][ICellSpan.ROW] = 1;
}
最后是比較重要的合并和拆分方法,它根據傳入的需要合并和拆分的行和列計算,得出數組新的值:
@Override
publicvoid combine(int[] rows, int[] columns) {
先取得開始比較的起點:
int rowSpan = rows.length;
int columnSpan = columns.length;
int startRow = rows[0];
int startColumn = columns[0];
對于需要修改的值比較并賦予新的:
for (int i = 0, ii = 0; i < rowSpan; i++, ii--) {
for (int j = 0, jj = 0; j < columnSpan; j++, jj--) {
span[startRow + i][startColumn + j][ICellSpan.COLUMN] = jj;
span[startRow + i][startColumn + j][ICellSpan.ROW] = ii;
}
}
最后設置新的數組值:
span[startRow][startColumn][ICellSpan.COLUMN] = columnSpan;
span[startRow][startColumn][ICellSpan.ROW] = rowSpan;
這樣新的span就形成了,當畫面repaint時,UI會根據新的span的值決定那個單元格的Border需要繪制,那個需要擦去,這樣就單元格方面完成了拆分,至于持分后內容的顯示則是通過Rebderer控制的.
這里我們的Rebderer類十分簡單,我們就不實現任何效果了,只確定合并后的顯示問題:
publicclass CustomCellRenderer extends JLabel implements TableCellRenderer {
它的接口實現方法
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
除了定義顏色背景色的基本屬性外,最主要的是設置顯示:
setText((value == null) ? "" : value.toString());
然后就是很重要的CustomCellUI類了,它根據前面CustomCell類的span來繪制合并單元格后的顯示問題:
/**
*BasicTableUIimplementation,paintthespantableui.
*/
publicclass CustomCellUI extends BasicTableUI {
它重寫BasicTableUI的paint方法進行自己的UI繪制,
/**
*Paintarepresentationofthetableinstancethatwasset
*ininstallUI().
*/
@Override
publicvoid paint(Graphics g, JComponent c) {
首先取得舊的繪制邊框:
Rectangle oldClipBounds = g.getClipBounds();
Rectangle clipBounds = new Rectangle(oldClipBounds);
然后根據JTable的寬度比較決定繪制的寬度,
int tableWidth = table.getColumnModel().getTotalColumnWidth();
clipBounds.width = Math.min(clipBounds.width, tableWidth);
g.setClip(clipBounds);
取得行的邊框
Rectangle rowRect = new Rectangle(0, 0, tableWidth, table
.getRowHeight() + table.getRowMargin());
rowRect.y = firstIndex * rowRect.height;
然后開始繪制行:
先去的需要繪制的合并單元格的屬性:
CustomTableModel tableModel = (CustomTableModel) table
.getModel();
ICellSpan cellAtt = (ICellSpan) tableModel.getCellAttribute();
然后算出本行內那幾個列需要合并:
cellRow = row + cellAtt.getSpan(row, column)[ICellSpan.ROW];
cellColumn = column
+ cellAtt.getSpan(row, column)[ICellSpan.COLUMN];
最后就是繪制具體的單元格了:
Color c = g.getColor();
g.setColor(table.getGridColor());
g.drawRect(cellRect.x, cellRect.y, cellRect.width - 1,
cellRect.height - 1);
g.setColor(c);
cellRect.setBounds(cellRect.x + spacingWidth / 2, cellRect.y
+ spacingHeight / 2, cellRect.width - spacingWidth,
cellRect.height - spacingHeight);
不僅如此還需要控制編輯狀態和普通狀態有Renderer的顯示問題:
Component component = table.getEditorComponent();
component.setBounds(cellRect);
component.validate();
//renderer
rendererPane.paintComponent(g, component, table, cellRect.x,
cellRect.y, cellRect.width, cellRect.height, true);
到這里單元格合并后的顯示就完成了,還有就是JTable的顯示和TableModel的數據設置,先看TableModel:
我們繼承DefaultTableModel類,
publicclass CustomTableModel extends DefaultTableModel {
它的構造函數和DefaultTableModel一樣,最終都會轉換為Vector,
public CustomTableModel(Vector<Vector<?>> data, Vector<?> columnNames) {
addDataVector(data, columnNames);
}
在它的addDataVector方法里,不但要給TableModel的數據集賦:
dataVector = new Vector<Vector<?>>(0);
setColumnIdentifiers(columnNames);
dataVector = newData;
還要根據行和列做成我們的CustomCell:
cellAtt = new CustomCell (dataVector.size(), columnIdentifiers
.size());
同樣的TableModel的addColumn、addRow、insertRow方法,我們都需要復寫修改,加上我們自己Cell的增加方法:
cellAtt.addRow();
cellAtt.insertRow(row);
cellAtt.addColumn();
最后我們修改的是JTable類,我們繼承它,設置它的UI和鼠標行和列的響應:
publicclass CustomTabel extends JTable {
構造函數和JTable相同,只不過我們需要設置自己的UI,
setUI(new CustomCellUI());
然后復寫rowAtPoint和columnAtPoint得到鼠標點擊時我們真正的行列:
@Override
publicint rowAtPoint(Point point) {
首先是取得當前所在行列和合并單元格的值:
int row = point.y / (rowHeight + rowMargin);
int column = getColumnModel().getColumnIndexAtX(point.x);
ICellSpan cellAtt = (ICellSpan) ((CustomTableModel) getModel())
.getCellAttribute();
然后取得實際的:
retValue[ICellSpan.COLUMN] = column
+ cellAtt.getSpan(row, column)[ICellSpan.COLUMN];
retValue[ICellSpan.ROW] = row
+ cellAtt.getSpan(row, column)[ICellSpan.ROW];
最后需要重寫的是方法,它保證了合并單元格后的行和列和Header的對應,不會因為去掉了單元格的Boder線使不能對齊:
@Override
public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
和UI里的paint方法一樣就是計算補足沒有Border的間隙,一般每合并一個加上1就可以了.
到這里為止,合并單元格的JTable就算完成了,但是比較麻煩的是因為它并不知道我們合并那個,不會主動給我們合并,需要我們自己去調用combine方法,比較復雜,而實際使用的時候,我們想告訴數據是什么樣子的希望JTable自己合并,因此寫了一個SpanCellTablePanel類,只需傳入數據就可以自己合并了.
publicclass SpanCellTablePanel extends JPanel {
繼承Jpanel確保我們可以和一個普通的JPanel一樣使用它.
它初始化TableModel并構建了JTable:
CustomTableModel model = new CustomTableModel(datas, convertToVector(columnTitle));
CustomTabel table = new CustomTabel(model)
提供了我們取得JTable的方法:
public JTable getTable() {
returntable;
}
提供一個我們給定數據轉換為合并的數據的方法:
privateint[][] combineSpanData(Vector<Vector<?>> datas) {
然后根據數據合并單元格顯示出來:
for (int i = 0; i < columns.length; i++) {
for (int t = 0; t < spanArray.length; t++) {
((ICellSpan) cellAtt).combine(spanArray[t],
newint[] { columns[i] });
}
}
table.clearSelection();
table.revalidate();
table.repaint();
最后就是使用了,只需要要傳入我們的數據就可以構建出可合并的JTable了:
spanTablePanel = new SpanCellTablePanel(createTestData());
當我們實際使用時可能希望選中任何一個單元格都選中目前處于的最大列全選擇,簡單的加個
SelectionListener就可以了
spanTablePanel.getTable().getSelectionModel().addListSelectionListener(
在事件里處理選中:
@Override
publicvoid valueChanged(ListSelectionEvent e) {
spanTablePanel.getTable().getSelectionModel()
.setSelectionInterval(allSelectRows[0],
llSelectRows[allSelectRows.length - 1]);
到此為之,對單元格的操作就基本結束了,以后看到或者想到別的再補充,JTable剩下的比較復雜的就是JtableHeader了,它也可以設置Rendere和Editor,也可以合并和拆分,可以設置特殊組件,下次就開始JtableHeader.
posted on 2010-04-12 21:54 zeyuphoenix 閱讀(4237) 評論(0) 編輯 收藏 所屬分類: JTable的使用