JTableHeader的單個表頭最復雜的操作也就是Renderer渲染和Editor編輯,然后增加事件處理和懸浮框提示,最多再加點特殊顯示效果,這和JTable單元格的操作相同,在前面的例子里都已經講過了,這里就剩下最后一個也是關于JTableHeader表頭的操作了, 表頭單元格的合并和拆分.
JTableHeader的單個表頭可編輯時可以把它看做一個JTextField,不可操作時可以看做一個JLabel,對于表頭的合并和拆分操作來說就是把JLabel或JTextField進行合并和拆分的過程.JTable表頭的合并簡單來說就是把你選定的要合并的表頭的邊線擦掉,然后調整寬度和高度,再在這幾個合并的表頭外圍畫一個新的邊線,然后設置JTableHeader的UI,刷新就可以了,和JTable的單元格基本相同,唯一的區別就是JTableHeader的表頭不像單元格那個容易得到和處理,我們需要定義數據結構來存儲它.
先看完成后的效果:
首先是合并一行的多個單元格為一個的界面:
再就是多行多列的合并了:
這兩個例子,實現是一樣的只是因為數據結構不一樣.
最后一個則是和前面單元格合并組成的例子,我們綜合前面的單元格合并,結合這次的JTableHeader合并,做一個綜合的合并的例子:
然后看工程的目錄:
首先的定義一個數據結構,存儲我們合并的JTableHeader.
需要理解的是,雖然看到的是一個大的單元格,但是其實它也是幾個JTableHeader,只是去掉了其內的邊框.所以對我們的合并的JTableHeader來說,需要定義一個數據結構來存儲它們的最小分子,當然它們的Renderer也存儲了,
/**
*thetableheaderthathavecolumngroup.
*/
publicclass ColumnGroup {
看它的屬性:
/**headerrenderer.*/
private TableCellRenderer renderer = null;
這個是合并的JTableHeader的Renderer,這里我們簡單化,我們就不像前面寫Renderer和Editor那樣分開存儲了,我們假設這個JTableHeader使用同一類的Renderer,如果你想實現不一樣的Renderer,你可以把它們定義成數組(PS:這樣效果會比較怪異,一個合并的單元格包含了幾個組件).
/**theheadergroup.*/
private Vector<Object> vector = null;
這個是合并的單元格的各個實際的最小單元格存儲結構.
/**headervalue.*/
private String text = null;
這個是合并后單元格顯示的文本信息
/**thehiddenborderwidth*/
privateintmargin = 0;
這個是合并的單元格內部兩個最小JTableHeader的間隙,其實就是去掉線后那個Border.
除了提供各個屬性的Get和Set方法外,還提供了增加單元格add:
/**
*addheaderorgrouptocolumngroup.
*/
publicvoid add(Object obj) {
if (obj == null) {
return;
}
v.addElement(obj);
}
取得合并后的單元格的大小getSize:
/**
*getheadergroupsize.
*/
public Dimension
getSize(JTable table) {
這個方法需要計算,首先是取得一個沒有合并的最小單元格的JTableHeader的大小,通過Renderer取得組件:
Component comp = renderer.getTableCellRendererComponent(table,
getHeaderValue(), false, false, -1, -1);
int height =
comp.getPreferredSize().height;
寬度需要計算合并的還要加上間隙:
Enumeration<Object> enumTemp = v.elements();
while
(enumTemp.hasMoreElements()) {
TableColumn aColumn = (TableColumn) obj;
width += aColumn.getWidth();
width
+= margin;
最后取得這個合并的JTableHeader的大小:
returnnew Dimension(width,
height);
還有一個比較重要的方法是根據JTable的某一列取得它的所有的包含列,這個主要用于繪制:
public Vector<ColumnGroup> getColumnGroups(TableColumn
column,
Vector<ColumnGroup>
group) {
通過遞歸判斷列到底屬于那個ColumnGroup:
group.addElement(this);
if (v.contains(column)) {
return group;
}
沒有找的則是Null.
然后我們看的類是MyTableColumn,它繼承于TableColumn,主要是JTableHeader的編輯屬性和Editor.
publicclass MyTableColumn extends TableColumn {
主要屬性:
/**tableheadereditor.*/
private TableCellEditor headerEditor;
/**isheadereditable.*/
privatebooleanisHeaderEditable;
方法只是簡單的Get和Set設置,設置了默認編輯與否和默認的JTableHeader的Editor.
其實也是一個簡單的類,主要是描述JTableHeader的Renderer的,這個在之前我們就介紹了,大概寫下:
publicclass MyHeaderRenderer extends JLabel implements
TableCellRenderer {
實現默認的方法:
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row, int column) {
首先是取得JTableHeader:
// set some color font about header.
JTableHeader header =
table.getTableHeader();
設置屬性:
setForeground(fgColor);
setBackground(bgColor);
setFont(header.getFont());
還有設置高度,和前面的那個Renderer專題一樣:
setPreferredSize(new
Dimension(getWidth(), headerHeight));
最后就是兩個最重要的類,JTableHeader和它的UI的繪制:
先看UI,我們繼承于BasicTableHeaderUI:
/**
*BasicTableHeaderUIimplementation.
*/
publicclass MyGroupTableHeaderUI extends BasicTableHeaderUI {
它重寫BasicTableHeaderUI的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);
取得行的邊框
((MyTableHeader) header).setColumnMargin();
Dimension size = header.getSize();
然后開始繪制行:
先去的需要繪制的合并JTableHeader的屬性:
Enumeration<?> cGroups = ((MyTableHeader) header)
.getColumnGroups(aColumn);
然后算出本行內那幾個列需要合并:
ColumnGroup cGroup = (ColumnGroup) cGroups.nextElement();
Rectangle groupRect = (Rectangle) h.get(cGroup);
if (groupRect == null) {
icount ++;
groupRect = new Rectangle(cellRect);
Dimension d = cGroup.getSize(header.getTable());
groupRect.width = d.width - icount;
groupRect.height = d.height;
h.put(cGroup, groupRect);
}
最后就是繪制具體的單元格了:
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的顯示問題:
TableCellRenderer renderer =
cGroup.getHeaderRenderer();
Component
component = renderer.getTableCellRendererComponent(header
.getTable(), cGroup.getHeaderValue(), false, false, -1, -1);
rendererPane.add(component);
rendererPane.paintComponent(g, component, header, cellRect.x,
cellRect.y, cellRect.width, cellRect.height, true);
到這里JTableHeader合并后的顯示繪制就完成了,然后就是它的大小的顯示:
@Override
public Dimension getPreferredSize(JComponent c) {
long width = 0;
Enumeration<?> enumeration = header.getColumnModel().getColumns();
while (enumeration.hasMoreElements()) {
TableColumn aColumn = (TableColumn)
enumeration.nextElement();
width = width + aColumn.getPreferredWidth();
}
return createHeaderSize(width);
}
JTableHeader和單元格合并不同的一點是它的事件比較復雜,我們需要重寫寫它的MouseInputHandler
/**
*Thisinnerclassismarked"public"duetoacompilerbug. * Thisclassshouldbetreatedasa"protected"innerclass.
*InstantiateitonlywithinsubclassesofBasicTableUI.
*/
privateclass MouseInputHandler extends
BasicTableHeaderUI.MouseInputHandler {
它提供一個屬性,
/**theshowcomponent.*/
private Component dispatchComponent = null;
然后在鼠標事件上加上我們自己的處理:
@Override
publicvoid mousePressed(MouseEvent e) {
根據鼠標的位置取得我們合并后繪制的單元格:
Point p =
e.getPoint();
TableColumnModel columnModel = header.getColumnModel();
int index = columnModel.getColumnIndexAtX(p.x);
if (index != -1) {
if (header.editCellAt(index, e)) {
setDispatchComponent(e);
設置完我們的顯示后,再把事件下發:
MouseEvent e2 = SwingUtilities.convertMouseEvent(header, e,
dispatchComponent);
dispatchComponent.dispatchEvent(e2);
其它的鼠標事件也一樣處理,添加我們自己的顯示.
然后就是最重要的JTableHeader了,我們重寫它:
publicclass MyTableHeader extends JTableHeader implements
CellEditorListener {
重寫updateUI方法,設置我們自己的UI:
@Override
publicvoid updateUI() {
setUI(new MyGroupTableHeaderUI());
resizeAndRepaint();
invalidate();
}
同時提供setColumnMargin、addColumnGroup、setCellEditor等方法設置JTableHeader的屬性和Editor.
然后就是設置JTableHeader編輯狀態和編輯后狀態的設置了:
首先根據鼠標事件取得是否是需要編輯的JTableHeader.
publicboolean editCellAt(int index, EventObject e) {
在可編輯的JTableHeader的單元格增加事件:
TableCellEditor editor = getCellEditor(index);
if (editor != null && editor.isCellEditable(e)) {
editorComp = prepareEditor(editor, index);
editorComp.setBounds(getHeaderRect(index));
add(editorComp);
editorComp.validate();
setCellEditor(editor);
setEditingColumn(index);
editor.addCellEditorListener(this);
returntrue;
}
重寫editingStopped方法和editingCanceled方法.編輯狀態停止后設置顯示:
@Override
publicvoid editingStopped(ChangeEvent e) {
TableCellEditor editor = getCellEditor();
if (editor != null) {
Object value = editor.getCellEditorValue();
int index = getEditingColumn();
columnModel.getColumn(index).setHeaderValue(value);
removeEditor();
}
}
@Override
publicvoid editingCanceled(ChangeEvent e) {
removeEditor();
}
最后就是使用了,雖然JTableHeader設置已經很麻煩了,但是設置顯示數據更麻煩,因此不到萬不得已不要用了,它基本不支持,基本不可能通過后期數據生成,一般用也是做成報表之類,但報表的JasperReport做的更好,所以雖然在這里寫,只是算一個思路.
使用首先要設置JTableHeader,構造JTableHeader的數據結構:
@Override
protected JTableHeader createDefaultTableHeader() {
returnnew MyTableHeader(columnModel);
}
TableColumnModel cm = table.getColumnModel();
ColumnGroup g_name = new ColumnGroup("Name");
g_name.add(cm.getColumn(1));
取得JTableHeader,設置合并的數據結構:
MyTableHeader header = (MyTableHeader) table
.getTableHeader();
header.addColumnGroup(g_name);
然后設置Renderer和UI:
TableCellRenderer renderer = new MyHeaderRenderer();
model.getColumn(i).setHeaderRenderer(renderer);
table.getTableHeader().setUI(new MyGroupTableHeaderUI());
其它的就和一個普通的JTable一樣了.
我們可以綜合單元格合并和JTableHeader合并,這樣就可以作出復雜的JTable了,如上面最后那張圖.