JTable的單個單元格最復(fù)雜的操作也就是Renderer渲染和Editor編輯,然后增加事件處理和懸浮框提示,最多再加點特殊顯示效果,在前面的例子里都已經(jīng)講過了,這里就剩下最后一個關(guān)于單元格的操作了,單元格的合并和拆分.
JTable的單元格可編輯時可以把它看做一個JTextField,不可操作時可以看做一個JLabel,對于單元格的合并和拆分操作來說就是把JLabel或JTextField進(jìn)行合并和拆分的過程.JTable單元格的合并簡單來說就是把你選定的要合并的單元格的邊線擦掉,然后調(diào)整寬度和高度,再在這幾個合并的單元格外圍畫一個新的邊線,然后設(shè)置JTable的UI,刷新就可以了.
先看完成后的界面:
然后是工程的目錄結(jié)構(gòu):
其中CustomCell就合并后的Cell,它儲存了當(dāng)前那幾個Cell被合并,以及合并后的寬和高用于計算.CustomCellRenderer就是合并后的Cell的Renderer了,它用于合并后Cell內(nèi)容的顯示,CustomCellUI則是合并Cell的UI了,這是主要的合并Cell用到的類,剩下的兩個接口ICellAttribute和ICellSpan則是合并Cell的接口,里面定義Cell增刪和更新的方法,而SpanCellTablePanel類是因為做成的合并單元格JTable很難使用,提供的輔助類,傳入數(shù)據(jù)則把JTable創(chuàng)建出來,并根據(jù)數(shù)據(jù)實現(xiàn)了合并單元格.
先看接口IcellSpan,它提供設(shè)置單元格是否可見,單元格的合并和拆分方法,單元格拆分的Index方法需要實現(xiàn)類完成.
/**
*theinterfacethataboutcellspan.
*/
publicinterface ICellSpan {
方法如下:
/**
*getcellSpan.
*/
publicint[] getSpan(int row, int column);
/**
*setcellspan.
*/
publicvoid setSpan(int[] span, int row, int column);
這兩個方法是提供JTable真正的行和列和合并后的行和列的對應(yīng)關(guān)系.
/**
*iscellvisible.
*/
publicboolean isVisible(int row, int column);
/**
*wherecombine.
*/
publicvoid combine(int[] rows, int[] columns);
/**
*wheresplit.
*/
publicvoid split(int row, int column);
提供當(dāng)前Jtable那幾個行和列合并;以及合并后的某一行和列的拆分.
然后是IcellAttribute接口,它提供增加行、增加列、以及插入行、取得和設(shè)置大小(不是物理大小,是相對于JTable的大小)的方法:
/**
*theinterfacethataboutcellattribute.
*/
publicinterface ICellAttribute {
再看方法:
/**
*addcolumntocell.
*/
publicvoid addColumn();
/**
*addrowtocell.
*/
publicvoid addRow();
/**
*insertrowtocell
*/
publicvoid insertRow(int row);
這三個方法提供行和列的增加操作
/**
*getcellsize.
*/
public Dimension getSize();
/**
*setcellsize.
*/
publicvoid setSize(Dimension
size);
這兩個方法提供單元格大小的設(shè)置和取得.
再看實現(xiàn)這兩個接口的類CustomCell,它是合并后的單元格的信息保存類:
/**
*setcellAttributespanandsoon.
*/
publicclass CustomCell implements ICellAttribute, ICellSpan {
有三個屬性:
/**cellwidth.*/
privateintrowSize = 0;
/**cellheight.*/
privateintcolumnSize = 0;
/**cellspans.*/
privateint[][][] span = null;
分別保存合并后單元格的寬度和高度(這個寬和高的意思是在兩個方向上有幾個JTable的單元格大小),以及合并的單元格是由原本JTable的那幾個行和列組成的.
先是構(gòu)造函數(shù):
/**
*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;
}
}
}
然后就是實現(xiàn)接口的方法,
@Override
publicint[] getSpan(int row, int column) {
返回span數(shù)組對應(yīng)的行和列:
returnspan[row][column];
@Override
publicvoid setSpan(int[] span, int row, int column) {
設(shè)置span數(shù)組對應(yīng)的行和列:
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;
然后創(chuàng)建新的:
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;
}
最后是比較重要的合并和拆分方法,它根據(jù)傳入的需要合并和拆分的行和列計算,得出數(shù)組新的值:
@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;
}
}
最后設(shè)置新的數(shù)組值:
span[startRow][startColumn][ICellSpan.COLUMN] = columnSpan;
span[startRow][startColumn][ICellSpan.ROW] = rowSpan;
這樣新的span就形成了,當(dāng)畫面repaint時,UI會根據(jù)新的span的值決定那個單元格的Border需要繪制,那個需要擦去,這樣就單元格方面完成了拆分,至于持分后內(nèi)容的顯示則是通過Rebderer控制的.
這里我們的Rebderer類十分簡單,我們就不實現(xiàn)任何效果了,只確定合并后的顯示問題:
publicclass CustomCellRenderer extends JLabel implements
TableCellRenderer {
它的接口實現(xiàn)方法
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
除了定義顏色背景色的基本屬性外,最主要的是設(shè)置顯示:
setText((value == null) ? "" :
value.toString());
然后就是很重要的CustomCellUI類了,它根據(jù)前面CustomCell類的span來繪制合并單元格后的顯示問題:
/**
*BasicTableUIimplementation,paintthespantableui.
*/
publicclass CustomCellUI extends BasicTableUI {
它重寫BasicTableUI的paint方法進(jìn)行自己的UI繪制,
/**
*Paintarepresentationofthetableinstancethatwasset
*ininstallUI().
*/
@Override
publicvoid paint(Graphics g, JComponent c) {
首先取得舊的繪制邊框:
Rectangle
oldClipBounds = g.getClipBounds();
Rectangle
clipBounds = new Rectangle(oldClipBounds);
然后根據(jù)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();
然后算出本行內(nèi)那幾個列需要合并:
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);
不僅如此還需要控制編輯狀態(tài)和普通狀態(tài)有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的數(shù)據(jù)設(shè)置,先看TableModel:
我們繼承DefaultTableModel類,
publicclass CustomTableModel extends DefaultTableModel {
它的構(gòu)造函數(shù)和DefaultTableModel一樣,最終都會轉(zhuǎn)換為Vector,
public CustomTableModel(Vector<Vector<?>> data,
Vector<?> columnNames) {
addDataVector(data, columnNames);
}
在它的addDataVector方法里,不但要給TableModel的數(shù)據(jù)集賦:
dataVector = new Vector<Vector<?>>(0);
setColumnIdentifiers(columnNames);
dataVector = newData;
還要根據(jù)行和列做成我們的CustomCell:
cellAtt = new CustomCell
(dataVector.size(), columnIdentifiers
.size());
同樣的TableModel的addColumn、addRow、insertRow方法,我們都需要復(fù)寫修改,加上我們自己Cell的增加方法:
cellAtt.addRow();
cellAtt.insertRow(row);
cellAtt.addColumn();
最后我們修改的是JTable類,我們繼承它,設(shè)置它的UI和鼠標(biāo)行和列的響應(yīng):
publicclass CustomTabel extends JTable {
構(gòu)造函數(shù)和JTable相同,只不過我們需要設(shè)置自己的UI,
setUI(new CustomCellUI());
然后復(fù)寫rowAtPoint和columnAtPoint得到鼠標(biāo)點擊時我們真正的行列:
@Override
publicint rowAtPoint(Point point) {
首先是取得當(dāng)前所在行列和合并單元格的值:
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的對應(yīng),不會因為去掉了單元格的Boder線使不能對齊:
@Override
public Rectangle
getCellRect(int row, int column, boolean includeSpacing) {
和UI里的paint方法一樣就是計算補足沒有Border的間隙,一般每合并一個加上1就可以了.
到這里為止,合并單元格的JTable就算完成了,但是比較麻煩的是因為它并不知道我們合并那個,不會主動給我們合并,需要我們自己去調(diào)用combine方法,比較復(fù)雜,而實際使用的時候,我們想告訴數(shù)據(jù)是什么樣子的希望JTable自己合并,因此寫了一個SpanCellTablePanel類,只需傳入數(shù)據(jù)就可以自己合并了.
publicclass SpanCellTablePanel extends JPanel {
繼承Jpanel確保我們可以和一個普通的JPanel一樣使用它.
它初始化TableModel并構(gòu)建了JTable:
CustomTableModel model = new CustomTableModel(datas, convertToVector(columnTitle));
CustomTabel table = new CustomTabel(model)
提供了我們?nèi)〉?/span>JTable的方法:
public JTable getTable() {
returntable;
}
提供一個我們給定數(shù)據(jù)轉(zhuǎn)換為合并的數(shù)據(jù)的方法:
privateint[][] combineSpanData(Vector<Vector<?>> datas)
{
然后根據(jù)數(shù)據(jù)合并單元格顯示出來:
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();
最后就是使用了,只需要要傳入我們的數(shù)據(jù)就可以構(gòu)建出可合并的JTable了:
spanTablePanel = new SpanCellTablePanel(createTestData());
當(dāng)我們實際使用時可能希望選中任何一個單元格都選中目前處于的最大列全選擇,簡單的加個
SelectionListener就可以了
spanTablePanel.getTable().getSelectionModel().addListSelectionListener(
在事件里處理選中:
@Override
publicvoid
valueChanged(ListSelectionEvent e) {
spanTablePanel.getTable().getSelectionModel()
.setSelectionInterval(allSelectRows[0],
llSelectRows[allSelectRows.length - 1]);
到此為之,對單元格的操作就基本結(jié)束了,以后看到或者想到別的再補充,JTable剩下的比較復(fù)雜的就是JtableHeader了,它也可以設(shè)置Rendere和Editor,也可以合并和拆分,可以設(shè)置特殊組件,下次就開始JtableHeader.