在JTable中实现单元格鼠标悬停效果

类别:Java 点击:0 评论:0 推荐:

在Google上搜索实现这种效果的方法,只搜到一个网页,是国外的,一看,还得要给钱注册!
俗话说,自力更生,艰苦奋斗,就自己想了法子,拿来分享。衷心请各位大虾指点指点不足

我写了个程序,里面用到一个JTable,本着MVC的精神,而且考虑到单元格可能不是简简单单一个Label能表现的,就写了个表格渲染器的类,叫NoteLabelRenderer

代码如下:

//NoteLabelRenderer.java

package com.component;

import javax.swing.*;
import javax.swing.table.*;

import java.awt.*;

import com.data.Note;

/**
 * To render the cells showing the note
 * @author Allen Chue
 *
 */
public class NoteLabelRenderer extends JPanel implements TableCellRenderer {

 public static ImageIcon ATTACH_ICON=new ImageIcon("res/attach.gif");
 public static ImageIcon BLANK_ICON=new ImageIcon("res/blank.gif");
 
 private JLabel icon=new JLabel();
 private JLabel content=new JLabel();
 private JLabel attach=new JLabel();
 
 private String contentText,title;
 
 public NoteLabelRenderer() {
  super();
  setLayout(new BorderLayout());
  
  icon.setOpaque(true);
  content.setOpaque(true);
  attach.setOpaque(true);
  
  icon.setHorizontalAlignment(SwingConstants.CENTER);
  icon.setVerticalAlignment(SwingConstants.CENTER);
  content.setVerticalAlignment(SwingConstants.NORTH);
  attach.setHorizontalAlignment(SwingConstants.CENTER);
  attach.setVerticalAlignment(SwingConstants.CENTER);
  
  add(icon,BorderLayout.WEST);
  add(content,BorderLayout.CENTER);
  add(attach,BorderLayout.EAST);
  setSize(getPreferredSize());
 }
 
 /**
     *  Returns the component used for drawing the cell.  This method is
     *  used to configure the renderer appropriately before drawing.
     *
     * @param table  the <code>JTable</code> that is asking the
     *    renderer to draw; can be <code>null</code>
     * @param value  the value of the cell to be rendered.  It is
     *    up to the specific renderer to interpret
     *    and draw the value.  For example, if
     *    <code>value</code>
     *    is the string "true", it could be rendered as a
     *    string or it could be rendered as a check
     *    box that is checked.  <code>null</code> is a
     *    valid value
     * @param isSelected true if the cell is to be rendered with the
     *    selection highlighted; otherwise false
     * @param hasFocus if true, render cell appropriately.  For
     *    example, put a special border on the cell, if
     *    the cell can be edited, render in the color used
     *    to indicate editing
     * @param row         the row index of the cell being drawn.  When
     *    drawing the header, the value of
     *    <code>row</code> is -1
     * @param column         the column index of the cell being drawn
     */
 public Component getTableCellRendererComponent(JTable table, Object value,
   boolean isSelected, boolean hasFocus, int row, int column) {
  changeBackground(isSelected);
  setNoteText((Note)value,isSelected);
  return this;
 }
 
 private void changeBackground(boolean isSelected) {
  Color selectedColor=new Color(160,231,160);
  if (isSelected) {
   icon.setBackground(selectedColor);
   content.setBackground(selectedColor);
   attach.setBackground(selectedColor);
  }
  else {
   icon.setBackground(null);
   content.setBackground(null);
   attach.setBackground(null);
  }
 }
 
 /**
  * Show the note according to a note
  * @param n The <code>Note</code>
  * @param flag A flag variable. True for emphasizing the the
  * contents, while false for showing it normally
  */
 private void setNoteText(Note n, boolean flag) {
  String color=flag?"#ffffff":"#000000";
  /*
   ************************
   *****Set Icon Area******
   ************************
   */
  icon.setIcon(n.getIcon());
  /*
   ************************
   ****Set Content Area****
   ************************
   */
  title=n.getTitle();
  contentText=n.getContent();
  if (contentText.length()>120) {
   contentText=contentText.substring(0,115)+"...";
  }
  content.setText("<html><font" +
     " color=#116677>"+title+"</font><br>" +
     "<font color="+color+">"+contentText+"</font></html>");
  /*
   ***********************
   ***Set Attach Area*****
   ***********************
   */
  if (n.getAttach() != null) {
   attach.setIcon(ATTACH_ICON);
  }
  else {
   attach.setIcon(BLANK_ICON);
  }
 }
 public String getContent() {
  return this.contentText;
 }
 public String getTitle() {
  return this.title;
 }
}

这是个备忘的程序,一些有Note字眼的不用关心。

这个NoteLableRenderer继承了JPanel,是为了更好地对其中一些组件进行布局的控制(在这里,每个单元格里有三个JLabel),TableCellRender接口是必须要实现的
可以看到,实现鼠标点击单元格改变文字颜色和单元格(也就是一个JPanel)背景色并不难,只需要在getTableCellRendererComponent方法中判断传入的参数isSelected是否为true,然后对组件属性重新设置,返回修改过的Component即可。JTable在鼠标点击单元格时会重画表格,于是改变后的单元格即可显示出来。

我还想要实现单元格的悬停效果,也就是说,当鼠标移动至某个单元格上方时,该单元格可以变化颜色或者加个边框来突出显示,就想JButton可以设置RollOverIcon一样。我想当然地在这个渲染器里添加了鼠标监听,然而测试确发现不行...
   经过一番查阅、思考,我终于将解决的方法从单元格渲染器上转至JTable本身来,因为我发现JTable里竟然有个rowAtPoint(Point point)方法(高手可不要笑我啊)!于是,我就在表格上监听鼠标动作事件,在mouseMoved(MouseEvent e)方法中用int row=this.rowAt(e.getPoint())得到鼠标移动时当前鼠标在哪一行。这样的话,至少是迈了一大步,接下来的问题就是如何对鼠标下面的单元格进行属性的修改。

经过查看JTable的源码,发现里面有个public Component prepareRenderer方法调用了单元格渲染器里的getTableCellRenderer方法,用于在绘制表格时准备好单元格内放置的组件。既然它是public的,我就重载了这个方法,并使用了个自己感觉有点笨的方法,表格中加了个Vector,存储每个单元格是否在鼠标下面的信息(用Boolean),也就是说,每一时刻这个Vector中只有一个为true(同一时刻,只能有一个单元格在鼠标下面),其余为false,同时保持Vector的size与单元格行数相同。
重载的prepareRenderer方法体如下:

/**
  * This method overides the super one, in order to
  * add rollover effects to the <code>JTable</code>
  * A <code>Vector</code> stores information about
  * whether the cell is beneath the mouse. If the value
  * corresponding to the cell is true, this method will
  * return a <code>Component</code> with the border modified;
  * while false, the <code>Renderer</code> will be returned
  * unchanged after calling the super method
  */
 public Component prepareRenderer(TableCellRenderer renderer, int row,
   int column) {
  Component comp = super.prepareRenderer(renderer, row, column);
  if (((Boolean)flagMouseOver.get(row)).booleanValue()) {
   ((JComponent)comp).setBorder(BorderFactory.createLineBorder(Color.GRAY,1));
   return comp;
  }
  else {
   ((JComponent)comp).setBorder(null);
   return comp;
  }
 }

flagMouseOver即为保存单元格状态的Vector。而mouseMoved方法如下

public void mouseMoved(MouseEvent e) {//Add rollover effects
  int row=this.rowAtPoint(e.getPoint());
  if (mouseAtRow == -1) {
   //Init variable mouseAtRow
   mouseAtRow=row;
   flagMouseOver.set(mouseAtRow,new Boolean(true));
   //System.out.println("Mouse first moves to row "+(row+1));
   
   repaint();
   revalidate();
  }
  else if (row != mouseAtRow) {//The mouse has moved to a new row
   //Clear the border
   flagMouseOver.set(mouseAtRow,new Boolean(false));
   
   //Set new row's border
   flagMouseOver.set(row,new Boolean(true));
   
   //System.out.println("Mouse moves from row "+(mouseAtRow+1)+" to row "+(row+1));
   repaint();
   revalidate();
   mouseAtRow=row;
   
  }  
 }

mouseAtRow为一个int类型的变量,存储鼠标移动前在哪一行。当鼠标移动时,获得当前鼠标在哪一行(表格仅有一列),然后与mouseAtRow比较,若变化了,说明鼠标到了新的一行,同时更新flagMouseOver内的数据,紧接着用repaint和revalidate方法更新表格,实现了表格GUI的变化。这里悬停效果是添加边框,当然也可以改背景等等。

这是我的经历,希望有人提出意见,我非常欢迎!

                             Allen Chue

本文地址:http://com.8s8s.com/it/it10484.htm