2010年4月18日 星期日

JTable 表格(懸浮框提示)

JTableToolTip提示和其它的组件提示是一样的,因为它们都是继承于Jcomponent,当我们需要为我们的单元格实现ToolTip的时候,只需要复写它的getToolTipText方法就可以了,

看看Sun官方的例子:

//Implement table cell tool tips.

@Override

public String getToolTipText(MouseEvent e) {

取得鼠标的行和列:

java.awt.Point p = e.getPoint();

int rowIndex = rowAtPoint(p);

int colIndex = columnAtPoint(p);

int ealColumnIndex = convertColumnIndexToModel(colIndex);

设置你需要显示的ToolTip,然后返回

tip = ……;

returntip;

同样的JTableHeader也是如此:

protected JTableHeader createDefaultTableHeader() {

returnnew JTableHeader(columnModel) {

@Override

public String getToolTipText(MouseEvent e) {

这样不用其它设置,JTable的基本ToolTip就实现了,它虽然比较简单,但是最大的好处是不用自己考虑定位和显示的问题,很多时候也就可以了.

关于JTable的简单ToolTip提示就算是完成了,当我们只是简单的提示的时候,只需要复写JTablegetToolTipTextsetToolTipText方法就可以了,上个例子就是这样;但是当我们需要使我们的ToolTip提示不像Sun提供的那么单调,我们就需要自己来实现了.

这时候的重点已经不在JTable上了,而在于ToolTip上面,我们可以把我们需要呈现的ToolTip实现为一个JPanel,这样就可以在它的上面放置各种组件了,设置放图标都可以,这个时候就需要实现这个ToolTipUIManager,这方面有很多开源的实现,以后有时间的话开个专题专门来介绍ToolTip,这里不写了.

对于已经实现好的我们自己的ToolTip,我们需要做的是把它注册到我们的JTable上面,再根据鼠标的位置显示它,如下图所示,它可以有Title,可以有图片,可以有正文,甚至可以再加别的组件.如下图所示:

我们需要的是增加监听:

addMouseListener(this)

根据监听的状态处理ToolTip

@Override

publicvoid mouseExited(MouseEvent event) {

隐藏

@Override

publicvoid mousePressed(MouseEvent event) {

显示

@Override

publicvoid mouseMoved(MouseEvent event) {

判断状态,显示或者隐藏.

然后是判断位置:

// display directly below or above JTable band

location.x = screenLocation.x;

location.y = screenLocation.y + mouseEvent.getY() + 22;

location.x = screenLocation.x +mouseEvent.getX();

f ((location.y + size.height) > (sBounds.y + sBounds.height)) {

location.y = screenLocation.y - size.height;

最后是取得鼠标所在单元格的值传入显示:

Point p = mouseEvent.getPoint();

int row = rowAtPoint(p);

int col = columnAtPoint(p);

if (row == -1 || col == -1) {

returnnull;

}

Object data = getValueAt(row, col);

当然此时你可以对data做变换,从而显示需要的值.

使用很简单,注册就可以了.

setActionRichTooltip(new RichTooltip());

然后是另外一种ToolTip的效果,这个是一个开源的实现,只抓张图看看就算了,和前面的实现基本类似,还比那个简单,也是UI,Timer以及位置计算.如下图,代码在open就有:

ToolTip到这儿就算完了,但是实际使用中可能有这个问题,如下图:

这是因为我们经过设置Renderer渲染的单元格显示值和实际值不一样了.但我们很多时候需要看到的其实不是它的真实值,而是渲染后的值,可以通过渲染的实现类把它取回来:

先取得渲染类:

TableCellRenderer cellRenderer = table.getColumnModel().getColumn(column).getCellRenderer();

再去的渲染的控件:

Component component = cellRenderer.getTableCellRendererComponent(table,

table.getValueAt(row, column), false, false, row, column);

这个时候不知道这个控件是否有getText方法,通过反射判断,没有则设置为””

String text = "";

if (component != null) {

for (Method method : component.getClass().getMethods()) {

if (method.getName().equals("getText")) {

text = method.invoke(component).toString();

}

}

}

最后效果如图:

到此为止,所有关于JTableToolTip提示就完成了,当然我实现的都是一些基本的效果,比较复杂的效果,则需要你自己去绘制和实现ToolTip,说句简单的,技术完成了,以后就是玩色彩和审美了,做程序也就这样了,很多时候后者重要.



source: http://www.blogjava.net/zeyuphoenix/archive/2010/04/14/318380.html

JTable的Header合併

JTableHeader的单个表头最复杂的操作也就是Renderer渲染和Editor编辑,然后增加事件处理和悬浮框提示,最多再加点特殊显示效果,这和JTable单元格的操作相同,在前面的例子里都已经讲过了,这里就剩下最后一个也是关于JTableHeader表头的操作了, 表头单元格的合并和拆分.

JTableHeader的单个表头可编辑时可以把它看做一个JTextField,不可操作时可以看做一个JLabel,对于表头的合并和拆分操作来说就是把JLabelJTextField进行合并和拆分的过程.JTable表头的合并简单来说就是把你选定的要合并的表头的边线擦掉,然后调整宽度和高度,再在这几个合并的表头外围画一个新的边线,然后设置JTableHeaderUI,刷新就可以了,JTable的单元格基本相同,唯一的区别就是JTableHeader的表头不像单元格那个容易得到和处理,我们需要定义数据结构来存储它.

先看完成后的效果:

首先是合并一行的多个单元格为一个的界面:

再就是多行多列的合并了:

这两个例子,实现是一样的只是因为数据结构不一样.

最后一个则是和前面单元格合并组成的例子,我们综合前面的单元格合并,结合这次的JTableHeader合并,做一个综合的合并的例子:

然后看工程的目录:

首先的定义一个数据结构,存储我们合并的JTableHeader.

需要理解的是,虽然看到的是一个大的单元格,但是其实它也是几个JTableHeader,只是去掉了其内的边框.所以对我们的合并的JTableHeader来说,需要定义一个数据结构来存储它们的最小分子,当然它们的Renderer也存储了,

/**

*thetableheaderthathavecolumngroup.

*/

publicclass ColumnGroup {

看它的属性:

/**headerrenderer.*/

private TableCellRenderer renderer = null;

这个是合并的JTableHeaderRenderer,这里我们简单化,我们就不像前面写RendererEditor那样分开存储了,我们假设这个JTableHeader使用同一类的Renderer,如果你想实现不一样的Renderer,你可以把它们定义成数组(PS:这样效果会比较怪异,一个合并的单元格包含了几个组件).

/**theheadergroup.*/

private Vector vector = null;

这个是合并的单元格的各个实际的最小单元格存储结构.

/**headervalue.*/

private String text = null;

这个是合并后单元格显示的文本信息

/**thehiddenborderwidth*/

privateintmargin = 0;

这个是合并的单元格内部两个最小JTableHeader的间隙,其实就是去掉线后那个Border.

除了提供各个属性的GetSet方法外,还提供了增加单元格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 enumTemp = v.elements();

while (enumTemp.hasMoreElements()) {

TableColumn aColumn = (TableColumn) obj;

width += aColumn.getWidth();

width += margin;

最后取得这个合并的JTableHeader的大小:

returnnew Dimension(width, height);

还有一个比较重要的方法是根据JTable的某一列取得它的所有的包含列,这个主要用于绘制:

public Vector getColumnGroups(TableColumn column,

Vector 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;

方法只是简单的GetSet设置,设置了默认编辑与否和默认的JTableHeaderEditor.

其实也是一个简单的类,主要是描述JTableHeaderRenderer,这个在之前我们就介绍了,大概写下:

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 {

它重写BasicTableHeaderUIpaint方法进行自己的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();

}

同时提供setColumnMarginaddColumnGroupsetCellEditor等方法设置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);

然后设置RendererUI

TableCellRenderer renderer = new MyHeaderRenderer();

model.getColumn(i).setHeaderRenderer(renderer);

table.getTableHeader().setUI(new MyGroupTableHeaderUI());

其它的就和一个普通的JTable一样了.

我们可以综合单元格合并和JTableHeader合并,这样就可以作出复杂的JTable,如上面最后那张图.


source: http://www.blogjava.net/zeyuphoenix/archive/2010/04/18/318690.html