表头合并单元格. 照例不多说了, 看代码吧.
首先需要定义一个接口, 看看表头是怎么合并的
/** */
/**
* 列头组
*
* @author Brad.Wu
* @version 1.0
*/
public
interface
Group
...
{
/** *//**
* 获取所在行
*
* @return
*/
public int getRow();
/** *//**
* 获取所在列
*
* @return
*/
public int getColumn();
/** *//**
* 获取占列个数
*
* @return
*/
public int getColumnSpan();
/** *//**
* 获取占行个数
*
* @return
*/
public int getRowSpan();
/** *//**
* 获取文字
*
* @return
*/
public Object getHeaderValue();
}
这个和HTML的写法其实一样的. 主要就是每个Cell所在的位置, 占的行列数以及文字.
接下来是一个默认的实现. 其实不写接口也可以, 因为通常不会对表头做动作的.
/** */
/**
* 默认Group实现
*
* @author Brad.Wu
* @version 1.0
*/
public
class
DefaultGroup
implements
Group
...
{
private int row = 0;
private int column = 0;
private int rowSpan = 1;
private int columnSpan = 1;
private Object headerValue = null;
/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getRow()
*/
public int getRow() ...{
return this.row;
}
/** *//**
* @param row 要设置的 row。
*/
public void setRow(int row) ...{
this.row = row;
}
/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getColumn()
*/
public int getColumn() ...{
return this.column;
}
/** *//**
* @param column 要设置的 column。
*/
public void setColumn(int column) ...{
this.column = column;
}
/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getColumnSpan()
*/
public int getColumnSpan() ...{
return this.columnSpan;
}
/** *//**
* @param columnSpan 要设置的 columnSpan。
*/
public void setColumnSpan(int columnSpan) ...{
this.columnSpan = columnSpan;
}
/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getRowSpan()
*/
public int getRowSpan() ...{
return this.rowSpan;
}
/** *//**
* @param rowSpan 要设置的 rowSpan。
*/
public void setRowSpan(int rowSpan) ...{
this.rowSpan = rowSpan;
}
/**//*
* (非 Javadoc)
*
* @see com.eplat.realty.view.component.table.Group#getHeaderValue()
*/
public Object getHeaderValue() ...{
return this.headerValue;
}
/** *//**
* @param headerValue 要设置的 headerValue。
*/
public void setHeaderValue(Object headerValue) ...{
this.headerValue = headerValue;
}
}
重写一个表头组件
import
java.awt.Component;
import
java.awt.Rectangle;
import
java.util.ArrayList;
import
java.util.List;
import
javax.swing.JTable;
import
javax.swing.table.JTableHeader;
import
javax.swing.table.TableCellRenderer;
import
javax.swing.table.TableColumnModel;
/** */
/**
* 可以合并的列头
*
* @author Brad.Wu
* @version 1.0
*/
@SuppressWarnings(
"
serial
"
)
public
class
GroupableTableHeader
extends
JTableHeader
...
{
private int rowCount = 0;
private int columnCount = 0;
private List<Group> groups = new ArrayList<Group>();
public GroupableTableHeader() ...{
// 这个是必须的, 因为如果可以拖动列的位置, 那么一切都完蛋了.
// 如果你想实现这个功能, 那么只能你自己去做了, 我可不想做这个, 看上去超烦的
this.setReorderingAllowed(false);
}
/**//*
* (非 Javadoc)
*
* @see javax.swing.table.JTableHeader#updateUI()
*/
@Override
public void updateUI() ...{
setUI(new GroupableTableHeaderUI());
}
/**//*
* 获取指定行列的位置
*/
public Rectangle getHeaderRect(int row, int column) ...{
Rectangle r = new Rectangle();
TableColumnModel cm = getColumnModel();
Group group = this.getGroup(row, column);
r.height = getHeight();
if (column < 0) ...{
// x = width = 0;
if (!getComponentOrientation().isLeftToRight()) ...{
r.x = getWidthInRightToLeft();
}
} else if (column >= cm.getColumnCount()) ...{
if (getComponentOrientation().isLeftToRight()) ...{
r.x = getWidth();
}
} else ...{
for (int i = 0; i < group.getColumn(); i++) ...{
r.x += cm.getColumn(i).getWidth();
}
for (int i = group.getColumn(), j = group.getColumn() + group.getColumnSpan() - 1; i < j; i++) ...{
r.width += cm.getColumn(i).getWidth();
}
if (!getComponentOrientation().isLeftToRight()) ...{
r.x = getWidthInRightToLeft() - r.x - r.width;
}
// r.width = cm.getColumn(column).getWidth();
}
return r;
}
/** *//**
* 获取Group的Y位置
*
* @param group
* @return
*/
public int getYOfGroup(Group group) ...{
int row = group.getRow();
TableCellRenderer renderer = this.getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(getTable(), group.getHeaderValue(),
false, false, group.getRow(), group.getColumn());
return row * comp.getPreferredSize().height;
}
/** *//**
* 获取Group的高度
*
* @param group
* @return
*/
public int getHeightOfGroup(Group group) ...{
int rowSpan = group.getRowSpan();
TableCellRenderer renderer = this.getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(getTable(), group.getHeaderValue(),
false, false, group.getRow(), group.getColumn());
return rowSpan * comp.getPreferredSize().height;
}
private int getWidthInRightToLeft() ...{
if ((table != null) && (table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF)) ...{
return table.getWidth();
}
return super.getWidth();
}
/** *//**
* 增加Group
*
* @param group
*/
public void addGroup(Group group) ...{
groups.add(group);
int row = group.getRow();
int rowSpan = group.getRowSpan();
rowCount = Math.max(rowCount, row + rowSpan);
int column = group.getColumn();
int columnSpan = group.getColumnSpan();
columnCount = Math.max(columnCount, column + columnSpan);
}
/** *//**
* 移除所有Group
*/
public void removeAllGroups() ...{
groups.clear();
}
/** *//**
* 获取所有的Group
*
* @return
*/
public List<Group> getGroups() ...{
List<Group> list = new ArrayList<Group>();
list.addAll(groups);
return list;
}
/** *//**
* 获取指定列上的Group
*
* @param columnIndex
* @return
*/
public List<Group> getGroupsAtColumn(int columnIndex) ...{
List<Group> list = new ArrayList<Group>();
for (Group group : groups) ...{
int minColumnIndex = group.getColumn();
int maxColumnIndex = minColumnIndex + group.getColumnSpan() - 1;
if (minColumnIndex <= columnIndex && maxColumnIndex >= columnIndex) ...{
list.add(group);
}
}
return list;
}
/** *//**
* 获取指定行上的所有Group
*
* @param rowIndex
* @return
*/
public List<Group> getGroupsAtRow(int rowIndex) ...{
List<Group> list = new ArrayList<Group>();
for (Group group : groups) ...{
int minRowIndex = group.getRow();
int maxRowIndex = minRowIndex + group.getRowSpan() - 1;
if (minRowIndex <= rowIndex && maxRowIndex >= rowIndex) ...{
list.add(group);
}
}
return list;
}
/** *//**
* 获取行数
*
* @return
*/
public int getRowCount() ...{
return this.rowCount;
}
/** *//**
* @return Returns the columnCount.
*/
public int getColumnCount() ...{
return this.columnCount;
}
/**//*
* (非 Javadoc)
*
* @see javax.swing.table.JTableHeader#setTable(javax.swing.JTable)
*/
@Override
public void setTable(JTable table) ...{
super.setColumnModel(table.getColumnModel());
super.setTable(table);
}
/** *//**
* 获取指定行列的Group
*
* @param row
* @param column
* @return
*/
public Group getGroup(int row, int column) ...{
for (Group group : groups) ...{
int rowIndex = group.getRow();
int columnIndex = group.getColumn();
int rowSpan = group.getRowSpan();
int columnSpan = group.getColumnSpan();
if (rowIndex <= row && rowIndex + rowSpan > row && columnIndex <= column
&& columnIndex + columnSpan > column)
return group;
}
return null;
}
/** *//**
* (非 Javadoc)
* @see javax.swing.table.JTableHeader#createDefaultRenderer()
*/
@Override
protected TableCellRenderer createDefaultRenderer() ...{
return new TableHeaderRenderer();
}
}
里面用到的TableHeaderRenderer, 没什么花头的, 贴出来看看
import
java.awt.Color;
import
java.awt.Component;
import
java.awt.Dimension;
import
java.awt.Rectangle;
import
java.io.Serializable;
import
javax.swing.JLabel;
import
javax.swing.JTable;
import
javax.swing.UIManager;
import
javax.swing.table.DefaultTableCellRenderer;
import
javax.swing.table.JTableHeader;
import
javax.swing.table.TableCellRenderer;
import
com.eplat.realty.view.Constants;
import
com.eplat.realty.view.component.label.M2Label;
/** */
/**
* Created at 2006-9-5 16:02:46
* 表头描述器
*
* @author Brad.Wu
* @version 1.0
*/
@SuppressWarnings(
"
serial
"
)
public
class
TableHeaderRenderer
extends
M2Label
implements
TableCellRenderer, Serializable
...
{
/** *//**
* Creates a default table cell renderer.
*/
public TableHeaderRenderer() ...{
setOpaque(true);
setHorizontalAlignment(JLabel.CENTER);
}
// implements javax.swing.table.TableCellRenderer
/** *//**
* Returns the default table cell renderer.
*
* @param table the JTable
* @param value the value to assign to the cell at [row, column]
* @param isSelected true if cell is selected
* @param hasFocus true if cell has focus
* @param row the row of the cell to render
* @param column the column of the cell to render
* @return the default table cell renderer
*/
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) ...{
if (table != null) ...{
JTableHeader header = table.getTableHeader();
if (header != null) ...{
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());
}
}
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
setValue(value);
Dimension dim = getPreferredSize();
if (dim.height < Constants.TABLE_ROW_HEIGHT)
setPreferredSize(new Dimension(getPreferredSize().width, Constants.TABLE_ROW_HEIGHT));
return this;
}
/**//*
* The following methods are overridden as a performance measure to to prune code-paths are
* often called in the case of renders but which we know are unnecessary. Great care should be
* taken when writing your own renderer to weigh the benefits and drawbacks of overriding
* methods like these.
*/
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public boolean isOpaque() ...{
Color back = getBackground();
Component p = getParent();
if (p != null) ...{
p = p.getParent();
}
// p should now be the JTable.
boolean colorMatch = (back != null) && (p != null) && back.equals(p.getBackground())
&& p.isOpaque();
return !colorMatch && super.isOpaque();
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*
* @since 1.5
*/
public void invalidate() ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public void validate() ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public void revalidate() ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public void repaint(long tm, int x, int y, int width, int height) ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public void repaint(Rectangle r) ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*
* @since 1.5
*/
public void repaint() ...{
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) ...{
// Strings get interned...
if (propertyName == "text") ...{
super.firePropertyChange(propertyName, oldValue, newValue);
}
}
/** *//**
* Overridden for performance reasons. See the Implementation Note for
* more information.
*/
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) ...{
}
/** *//**
* Sets the String
object for the cell being rendered to value
.
*
* @param value the string value for this cell; if value is null
it sets the text
* value to an empty string
* @see JLabel#setText
*/
protected void setValue(Object value) ...{
setText((value == null) ? "" : value.toString());
}
/** *//**
* A subclass of DefaultTableCellRenderer
that implements UIResource
.
* DefaultTableCellRenderer
doesn't implement UIResource
directly
* so that applications can safely override the cellRenderer
property with
* DefaultTableCellRenderer
subclasses.
*
* Warning: Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is appropriate for short term
* storage or RMI between applications running the same version of Swing. As of 1.4, support for
* long term storage of all JavaBeansTM has been added to
* the java.beans
package. Please see {
@link java.beans.XMLEncoder}.
*/
public static class UIResource extends DefaultTableCellRenderer implements
javax.swing.plaf.UIResource ...{
}
}
最重要的UI登场, 哈哈
import
java.awt.Component;
import
java.awt.Dimension;
import
java.awt.Graphics;
import
java.awt.Point;
import
java.awt.Rectangle;
import
java.util.Enumeration;
import
java.util.List;
import
javax.swing.JComponent;
import
javax.swing.plaf.basic.BasicTableHeaderUI;
import
javax.swing.table.TableCellRenderer;
import
javax.swing.table.TableColumn;
import
javax.swing.table.TableColumnModel;
/** */
/**
* 可合并列头UI
*
* @author Brad.Wu
* @version 1.0
*/
public
class
GroupableTableHeaderUI
extends
BasicTableHeaderUI
...
{
/**//*
* (非 Javadoc)
*
* @see javax.swing.plaf.basic.BasicTableHeaderUI#paint(java.awt.Graphics,
* javax.swing.JComponent)
*/
@Override
public void paint(Graphics g, JComponent c) ...{
if (header.getColumnModel().getColumnCount() <= 0) ...{
return;
}
boolean ltr = header.getComponentOrientation().isLeftToRight();
Rectangle clip = g.getClipBounds();
Point left = clip.getLocation();
Point right = new Point(clip.x + clip.width - 1, clip.y);
TableColumnModel cm = header.getColumnModel();
int cMin = header.columnAtPoint(ltr ? left : right);
int cMax = header.columnAtPoint(ltr ? right : left);
// This should never happen.
if (cMin == -1) ...{
cMin = 0;
}
// If the table does not have enough columns to fill the view we'll get -1.
// Replace this with the index of the last column.
if (cMax == -1) ...{
cMax = cm.getColumnCount() - 1;
}
// TableColumn draggedColumn = header.getDraggedColumn();
int columnWidth;
// Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax);
TableColumn aColumn;
// if (ltr) {
// for (int column = cMin; column <= cMax; column++) {
// aColumn = cm.getColumn(column);
// columnWidth = aColumn.getWidth();
// cellRect.width = columnWidth;
// // if (aColumn != draggedColumn) {
// paintCell(g, cellRect, column);
// // }
// cellRect.x += columnWidth;
// }
// } else {
// for (int column = cMax; column >= cMin; column--) {
// aColumn = cm.getColumn(column);
// columnWidth = aColumn.getWidth();
// cellRect.width = columnWidth;
// // if (aColumn != draggedColumn) {
// paintCell(g, cellRect, column);
// // }
// cellRect.x += columnWidth;
// }
// }
GroupableTableHeader gHeader = (GroupableTableHeader) header;
for (int row = 0, rowCount = gHeader.getRowCount(); row < rowCount; row++) ...{
Rectangle cellRect = gHeader.getHeaderRect(row, ltr ? cMin : cMax);
if (ltr) ...{
for (int column = cMin; column <= cMax; column++) ...{
Group group = gHeader.getGroup(row, column);
cellRect.width = 0;
for (int from = group.getColumn(), to = from + group.getColumnSpan() - 1; from <= to; from++) ...{
aColumn = cm.getColumn(from);
columnWidth = aColumn.getWidth();
cellRect.width += columnWidth;
}
cellRect.y = gHeader.getYOfGroup(group);
cellRect.height = gHeader.getHeightOfGroup(group);
paintCell(g, cellRect, row, column);
cellRect.x += cellRect.width;
column += group.getColumnSpan() - 1;
}
} else ...{
for (int column = cMax; column >= cMin; column--) ...{
Group group = gHeader.getGroup(row, column);
cellRect.width = 0;
for (int from = group.getColumn(), to = from + group.getColumnSpan() - 1; from <= to; from++) ...{
aColumn = cm.getColumn(from);
columnWidth = aColumn.getWidth();
cellRect.width += columnWidth;
}
paintCell(g, cellRect, row, column);
cellRect.x += cellRect.width;
column -= group.getColumnSpan() - 1;
}
}
}
// Remove all components in the rendererPane.
rendererPane.removeAll();
}
/** *//**
* 描画指定行列
*
* @param g
* @param cellRect
* @param rowIndex
* @param columnIndex
*/
private void paintCell(Graphics g, Rectangle cellRect, int rowIndex, int columnIndex) ...{
Component component = getHeaderRenderer(rowIndex, columnIndex);
rendererPane.paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width,
cellRect.height, true);
}
/** *//**
* 获取指定行列的描述组件
*
* @param rowIndex
* @param columnIndex
* @return
*/
private Component getHeaderRenderer(int rowIndex, int columnIndex) ...{
GroupableTableHeader gHeader = (GroupableTableHeader) header;
Group group = gHeader.getGroup(rowIndex, columnIndex);
TableCellRenderer renderer = header.getDefaultRenderer();
return renderer.getTableCellRendererComponent(header.getTable(), group.getHeaderValue(),
false, false, -1, columnIndex);
}
/** *//**
* 获取列头的高度
*
* @return
*/
private int getHeaderHeight() ...{
int height = 0;
int tempHeight = 0;
GroupableTableHeader gHeader = (GroupableTableHeader) header;
TableColumnModel cm = header.getColumnModel();
for (int column = 0, columnCount = cm.getColumnCount(); column < columnCount; column++) ...{
tempHeight = 0;
List<Group> groups = gHeader.getGroupsAtColumn(column);
for (Group group : groups) ...{
TableCellRenderer renderer = gHeader.getDefaultRenderer();
Component comp = renderer.getTableCellRendererComponent(header.getTable(), group
.getHeaderValue(), false, false, -1, column);
int rendererHeight = comp.getPreferredSize().height;
tempHeight += rendererHeight;
}
height = Math.max(height, tempHeight);
}
return height;
}
private Dimension createHeaderSize(long width) ...{
// TableColumnModel columnModel = header.getColumnModel();
// None of the callers include the intercell spacing, do it here.
if (width > Integer.MAX_VALUE) ...{
width = Integer.MAX_VALUE;
}
return new Dimension((int) width, getHeaderHeight());
}
/** *//**
* Return the minimum size of the header. The minimum width is the sum of the minimum widths of
* each column (plus inter-cell spacing).
*/
public Dimension getMinimumSize(JComponent c) ...{
long width = 0;
Enumeration enumeration = header.getColumnModel().getColumns();
while (enumeration.hasMoreElements()) ...{
TableColumn aColumn = (TableColumn) enumeration.nextElement();
width = width + aColumn.getMinWidth();
}
return createHeaderSize(width);
}
/** *//**
* Return the preferred size of the header. The preferred height is the maximum of the preferred
* heights of all of the components provided by the header renderers. The preferred width is the
* sum of the preferred widths of each column (plus inter-cell spacing).
*/
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);
}
/** *//**
* Return the maximum size of the header. The maximum width is the sum of the maximum widths of
* each column (plus inter-cell spacing).
*/
public Dimension getMaximumSize(JComponent c) ...{
long width = 0;
Enumeration enumeration = header.getColumnModel().getColumns();
while (enumeration.hasMoreElements()) ...{
TableColumn aColumn = (TableColumn) enumeration.nextElement();
width = width + aColumn.getMaxWidth();
}
return createHeaderSize(width);
}
}
最后顺便贴一个测试程序, 可以看看怎么用法. 文档我也没有写过
import
java.awt.BorderLayout;
import
java.awt.HeadlessException;
import
javax.swing.JFrame;
import
javax.swing.JScrollPane;
import
javax.swing.JTable;
import
javax.swing.table.DefaultTableModel;
/** */
/**
* 合并列头测试
*
* @author Brad.Wu
* @version 1.0
*/
@SuppressWarnings(
"
serial
"
)
public
class
GroupableTableHeaderTest
extends
JFrame
...
{
/** *//**
* @param args
*/
public static void main(String[] args) ...{
GroupableTableHeaderTest test = new GroupableTableHeaderTest();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setSize(800, 600);
test.setVisible(true);
}
private DefaultTableModel tableModel = new DefaultTableModel() ...{
/**//*
* (非 Javadoc)
*
* @see javax.swing.table.DefaultTableModel#getColumnCount()
*/
@Override
public int getColumnCount() ...{
return 6;
}
/**//*
* (非 Javadoc)
*
* @see javax.swing.table.DefaultTableModel#getRowCount()
*/
@Override
public int getRowCount() ...{
return 2;
}
};
private JTable table = new JTable(tableModel);
private JScrollPane scroll = new JScrollPane(table);
/** *//**
* @throws HeadlessException
*/
public GroupableTableHeaderTest() throws HeadlessException ...{
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
GroupableTableHeader tableHeader = new GroupableTableHeader();
table.setTableHeader(tableHeader);
DefaultGroup group = new DefaultGroup();
group.setRow(0);
group.setRowSpan(2);
group.setColumn(0);
group.setHeaderValue("楼层");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(0);
group.setRowSpan(2);
group.setColumn(1);
group.setHeaderValue("水平/垂直系数");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(0);
group.setColumn(2);
group.setColumnSpan(2);
group.setHeaderValue("A & B");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(1);
group.setColumn(2);
group.setHeaderValue("Column A");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(1);
group.setColumn(3);
group.setHeaderValue("Column B");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(0);
group.setColumn(4);
group.setColumnSpan(2);
group.setHeaderValue("C & D");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(1);
group.setColumn(4);
group.setHeaderValue("Column C");
tableHeader.addGroup(group);
group = new DefaultGroup();
group.setRow(1);
group.setColumn(5);
group.setHeaderValue("Column D");
tableHeader.addGroup(group);
getContentPane().add(scroll, BorderLayout.CENTER);
}
}
OK了, 看看效果把.