没有错是世纪前的swing。

在使用Swing的时候有个问题一直没有解决,就是Swing自带的tooltip不会跟随鼠标进行移动,而且移动到边界就会遮挡的问题。JCompoent有个createTooltip()方法,但这个方法只能改变tooltip的外观,不能改变行为。事实上tooltip的行为和设置全都是由TooltipManager来进行,所以解决的方法只有自己撸一个类似于ToolTipManager了。

实现方法

从原来TooltipManager的实现原理来看(见Tooltipmanager源码),它是通过三个控制出现、持续和隐藏的线程,和JPopupFactory来实现的。

ToolTipManager() {
enterTimer = new Timer(750, new insideTimerAction());
enterTimer.setRepeats(false);
exitTimer = new Timer(500, new outsideTimerAction());
exitTimer.setRepeats(false);
insideTimer = new Timer(4000, new stillInsideTimerAction());
insideTimer.setRepeats(false); // create accessibility actions
postTip = KeyStroke.getKeyStroke(KeyEvent.VK_F1,Event.CTRL_MASK);
postTipAction = new Actions(Actions.SHOW);
hideTip = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0);
hideTipAction = new Actions(Actions.HIDE); moveBeforeEnterListener = new MoveBeforeEnterListener();
}

但我向做一个轻量级别的,即只用一个控制出现的线程,弹出层我不使用JPopupFactory,用的是JComponent,使用JlayeredPane来控制图层层叠次序。

实现之前

实现之前建议先了解JRootPane、JLayeredPane的相关知识。以及Java API文档对这两个的解释。

Oracle官方有文档:How to Use Root Panes、How to Use Layered Panes

概括来说,就是:一个Window(JDialog、JFrame等),窗口显示区域是JRootPane区域,如图,menu区域(可无)+ContentPane区域=JRootPane区域。

如果一个窗口没有设置menu,那么ContentPane区域=JRootPane区域。

如图,JRootPane里面有两个儿子:GlassPane,JLayeredPane两个图层。GlassPane的图层次序是0,代表顶层,JLayeredPane的次序是-1,代表底层。GlassPane默认是不显示的。JLayeredPane也有两个儿子:conentPane和JMenubar,其中contentPane是我们常用的图层。而我们要做的就是再给JlayeredPane加一个儿子,把ToolTip加进去,当然你也可以加在GlassPane里面。

(-1:底层 0:顶层)

JRootPane.java

public void setLayeredPane(JLayeredPane layered) {
...... this.add(layeredPane, -1);
} public void setGlassPane(Component glass) {
......
this.add(glassPane, 0);
......
}

JRootPane 的Layeredpane 和 GlassPane的初始化方法。

实现组件

1、对于默认JToolTip来说其实它就是一个JLabel,对于多行文本的显示比较差,往往需要使用HTML标记来使用。

所以我自己实现的组件使用了JTextPane,支持html、以及多行文本形式。

2、ToolTip需要给鼠标划过的组件添加侦听,当鼠标移除后要及时移除侦听,我还采用了一个变量记录上一个侦听,防止侦听溢出。

3、动态计算ToolTip位置,当移动到右、下边界时会分别平移到鼠标左、上,防止遮挡。

4、使用一个线程计时,实现弹出时间,和tooltipmanager不同的是,鼠标不移开组件,Tooltip就不会消失。

代码实现

PopTimingTip.java

 package;

 import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.LineBorder; public class PopTimingTip extends JComponent { private JPanel mainPanel;
private JTextPane textComponent;
private TipListener tipListener;
private Component tipParent;
private int initTime = 0;
private int lastTime = 0;
private int vanishTime = 0;
private JLayeredPane windowLayer;//窗口的遮罩层,不能随便修改,只作父对象应用
private Point constPoint = new Point(12, 5);//距离鼠标常量,防止鼠标遮挡
private Point constPoint2 = new Point(2,2);//常量2 防止离鼠标太近,触发mouseexit事件
private Timer tipTimer;
private TipTimerListener tipTimerListener;
private static PopTimingTip popTimingTip;
private JRootPane rootPane;//当前根面板
private Dimension curTipSize;
private int curConType; /**
* 单例外部不允许初始化
*/
private PopTimingTip() {
super();
initTip();
} public static PopTimingTip getInstance() {
if(popTimingTip == null) {
popTimingTip = new PopTimingTip();
}
return popTimingTip;
} private void initTip() {
this.setLayout(new BorderLayout());
this.setOpaque(false);
//this.setBorder(null);
this.setVisible(false);
textComponent = new JTextPane();
textComponent.setContentType("text/html");
textComponent.setBorder(new LineBorder(Color.BLACK));
textComponent.setBackground(new Color(245, 245, 245));
mainPanel = new JPanel(new BorderLayout());
mainPanel.add(textComponent, BorderLayout.CENTER);
this.add(mainPanel, BorderLayout.CENTER); tipTimerListener = new TipTimerListener();
tipTimerListener.state = 0; tipListener = new TipListener();
tipTimer = new Timer(0, tipTimerListener);
tipTimer.setRepeats(false); curTipSize = new Dimension(0,0);
}
public void showTip() {
this.setVisible(true);
}
/**
* 为某个组件设置tip
* @param parent 显示tooltip的对象
* @param text
*/
public void showTipText(JComponent parent, String text) {
if(parent == null) {
return;
}
//如果进入了新的组件,先从旧组件中移除侦听防止泄漏
if(tipParent != null && tipParent != parent) {
tipParent.removeMouseListener(tipListener);
tipParent.removeMouseMotionListener(tipListener);
}
tipParent = parent; rootPane = parent.getRootPane();
//防止异常获取不了根面板的情况
if(rootPane == null) {
return;
} JLayeredPane layerPane = rootPane.getLayeredPane();
//先从旧面板中移除tip
if(windowLayer != null && windowLayer != layerPane) {
windowLayer.remove(this);
}
windowLayer = layerPane;
//防止还有没有移除侦听的组件
tipParent.removeMouseListener(tipListener);
tipParent.removeMouseMotionListener(tipListener);
layerPane.remove(this);
//放置tip在遮罩窗口顶层
layerPane.add(this, JLayeredPane.POPUP_LAYER);
//窗口遮罩层添加侦听
tipParent.addMouseMotionListener(tipListener);
tipParent.addMouseListener(tipListener);
//测试侦听器数量
//System.out.println(tipParent.getMouseListeners().length + " " + tipParent.getMouseMotionListeners().length);
//设置tiptext
textComponent.setText(text);
mainPanel.doLayout();
//this.setPreferredSize(textComponent.getPreferredSize());
curTipSize = textComponent.getPreferredSize();
this.setSize(textComponent.getPreferredSize().width, textComponent.getPreferredSize().height);
} /**
* 初始化toolTip
* @param contentType 0:html 1:文本类型
* @param initTime 鼠标进入后等待时间
* @param lastTime 持续时间(未完成)
* @param vanishTime 鼠标移走后消失时间(未完成)
*/
public void setConfigure(int contentType, int initTime) {
if(contentType == 0 && curConType != contentType) {
textComponent.setContentType("text/html");
} else if(contentType ==1 && curConType != contentType) {
textComponent.setContentType("text/plain");
}
curConType = contentType;
this.initTime = initTime;
//this.vanishTime = vanishTime;
//this.lastTime = lastTime;
}
/**
* 坐标转换,标签跟随鼠标移动
*/
private void followWithMouse(MouseEvent e) {
if(windowLayer == null) {
return;
} Point screenPoint = e.getLocationOnScreen(); SwingUtilities.convertPointFromScreen(screenPoint, windowLayer); int newLocationX = screenPoint.x + constPoint.x;
int newLocationY = screenPoint.y + constPoint.y; Dimension tipSize = textComponent.getPreferredSize();
if(newLocationX + tipSize.width > rootPane.getWidth()) {
newLocationX = screenPoint.x - tipSize.width - constPoint2.x;
}
if(newLocationY + tipSize.height > rootPane.getHeight()) {
newLocationY = screenPoint.y - tipSize.height - constPoint2.y;
}
this.setLocation(newLocationX, newLocationY);
//textComponent.getPreferredSize()在html初始化计算的时候有问题,重算一次
if(!curTipSize.equals(textComponent.getPreferredSize())) {
this.setSize(textComponent.getPreferredSize().width, textComponent.getPreferredSize().height);
}
} private void setTipState(int state) {
tipTimer.stop();//停止上一次的任务
if(state == 0) {//进入组件,延迟显示
tipTimerListener.state = 0;
tipTimer.setInitialDelay(initTime);
tipTimer.start();
} else if(state == 1) {//鼠标移出,组件消失
tipTimerListener.state = 1;
PopTimingTip.this.setVisible(false);
}
} private class TipTimerListener implements ActionListener {
int state;
public void actionPerformed(ActionEvent e) {
if(state == 0) {
PopTimingTip.this.setVisible(true);
}
}
} /**
* 鼠标移除后及时清除侦听防止侦听器溢出
*/
private void removeTipAndListener() {
if(tipParent == null) {
return;
}
tipParent.removeMouseListener(tipListener);
tipParent.removeMouseMotionListener(tipListener);
if(windowLayer != null) {
windowLayer.remove(this);
}
} private class TipListener extends MouseAdapter {
public void mouseEntered(MouseEvent e) {
setTipState(0);
followWithMouse(e);
} /**
* 鼠标移出对象时,移除对象的侦听和ToolTip
*/
public void mouseExited(MouseEvent e) {
setTipState(1);
followWithMouse(e);
removeTipAndListener();
} //在组件上移动时触发
public void mouseMoved(MouseEvent e){
setTipState(0);
followWithMouse(e);
} public void mouseClicked(MouseEvent e) {
if((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {//右键点击,tip消失
setTipState(1);
followWithMouse(e);
removeTipAndListener();
}
}
} }

ToolTipTest.java

package swingpac;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder; public class ToolTipTest extends JFrame { private JPanel contentPane; /**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ToolTipTest frame = new ToolTipTest();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} /**
* Create the frame.
*/
public ToolTipTest() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
contentPane.setLayout(new FlowLayout());
initTip();
} private void initTip() {
JButton btn = null;
MListener m = new MListener();
for(int i=0; i<10; i++) {
btn = new JButton();
btn.setName("第"+i+"个按钮!");
btn.setText("按钮"+i);
btn.addMouseListener(m);
contentPane.add(btn);
}
} class MListener extends MouseAdapter {
public void mouseEntered(MouseEvent e) {
JButton btn = (JButton)e.getSource();
StringBuilder sb = new StringBuilder();
sb.append("当前进入的按钮是:\n")
.append(btn.getName()).append("\n")
.append("正在进行演示自定义Tooltip!\n")
.append("请自行查看源码"); PopTimingTip.getInstance().setConfigure(1, 300);
PopTimingTip.getInstance().showTipText(btn, sb.toString());
}
} }

效果演示(红色为鼠标位置)

遇到边界平移

总结

利用JLayeredPane还可以做出其它组件,比如弹出层录入等,个人感觉比Popup要轻量级,而且不会使界面失去焦点,阻塞用户操作。

Swing图层的应用——实现tooltip显示的更多相关文章

  1. ECharts图表tooltip显示时超出canvas图层解决方法

    我们在做ECharts图表的时候可能会遇到tooltip显示时超出了canvas图层范围,并且被其它z-index较高的div容器遮盖,这是悬浮展示信息就看不全,我们根据官网文档的配置项查询发现con ...

  2. [WPF]TextTrimming截断后,ToolTip显示完整信息

    文本过长被截断后,用ToolTip显示完整信息. 文本未被截断,则不显示ToolTip. 值转换器: public class TrimmedTextBlockVisibilityConverter ...

  3. echarts之字符云tooltip显示混乱问题的解决办法

    echarts字符云中tooltip显示混乱主要表现为一下两点: 1.字体与其显示框内容不对应鼠标识别错误 解决思路: 就是option里的数据要对value降序排序(这一点很关键,是必须的一步) 把 ...

  4. ArcGIS图层和要素的过滤显示

    ArcGIS可以设置动态地图服务(ArcGISDynamicMapServiceLayer)显示哪些图层,也可以设置每个图层根据某个属性字段的某些条件来进行过滤显示. 1.设置显示的图层 主要是通过A ...

  5. ArcGIS Server 10.2 实战(三)图层标注及图例中文显示乱码的解决

    发布的图层中不可避免的使用到中文来标注,默认设置下,ArcGIS Server不支持中文的,中文标注显示成乱码,主要是编码的问题,需要把手动把编码改为UTF-8. ArcGIS Server 10.2 ...

  6. Echarts 的悬浮框tooltip显示自定义格式化

    最近做的项目用到echarts雷达图,但是由于地市过多,遇到悬浮框显示问题被遮住 如图: 可以看到上面从兴安开始数据就被遮住了 为了解决这个被遮住的悬浮框,达到tooltip自定义格式 完成后的效果如 ...

  7. 使用tooltip显示jquery.validate.unobtrusive验证信息

    通过重写CSS实现使用tooltip显示jquery.validate.unobtrusive验证信息,效果如图: 1. 在ViewModel中定义验证规则 [Display(Name = " ...

  8. C# ArcgisEngine开发中,对一个图层进行过滤,只显示符合条件的要素

    转自原文 C# ArcgisEngine开发中,对一个图层进行过滤,只显示符合条件的要素 有时候,我们要对图层上的地物进行有选择性的显示,以此来满足实际的功能要求. 按下面介绍的方法可轻松实现图层属性 ...

  9. 利用动态图层实现数据的实时显示(arcEngine IDynamiclayer)

    marine 原文利用动态图层实现数据的实时显示(arcEngine IDynamiclayer) 说明:最近一个项目用到这方面知识,文章主要来至网络,后期会加入自己的开发心得.(以下的代码实例中,地 ...

随机推荐

  1. 智齿客服网页端接入文档V2.3

    产品介绍 智齿客服网页端接入提供以下两种部署方式. 一.网页组件(推荐) 通过智齿客服网站咨询组件,企业的用户可快捷联系到企业客服获取帮助.智齿客服网页组件提供强大的用户行为采集能力和系统对接能力,支 ...

  2. [bzoj1565][NOI2009]植物大战僵尸_网络流_拓扑排序

    植物大战僵尸 bzoj1565 题目大意:给你一张网格图,上面种着一些植物.你从网格的最右侧开始进攻.每个植物可以对僵尸提供能量或者消耗僵尸的能量.每个植物可以保护一个特定网格内的植物,如果一个植物被 ...

  3. 爬虫(scrapy中调试文件)

    在项目setting同级目录下创建py文件,代码如下: from scrapy.cmdline import execute import sys import os sys.path.append( ...

  4. 强烈推荐!!!Fiddler抓取https设置详解(图文)

    很多实用fiddler抓包,对于http来说不需太多纠结,随便设置下就能用,但是抓取https就死活抓不了, 诸如以下问题: creation of the root certificate was ...

  5. 线程池与Python中的GIL

    线程池是一个操作系统的概念,它是对多线程的一种优化. 多线程的时候,创建和销毁线程伴随着操作系统的开销,如果频繁创建/销毁线程,则会使效率大大降低. 而线程池,是先创建出一批线程放入池子里,需要创建线 ...

  6. Ubuntu16.04安装postgresql9.4及pgadmin3图形管理界面

    参考原文链接:http://www.cnblogs.com/sparkdev/p/5678874.html 安装前的检查 首先查看是否已经安装了旧版本: dpkg -l |grep postgresq ...

  7. TED - How To Get Better At The Things You Care About

    TED01 - How To Get Better At The Things You Care About 昨天我发布了攻克英语口语的宣言,今天就行动.TED是我们学习口语的好地方,本着学以致用的原 ...

  8. web.config中configSections section节 -Z

    由于最近一个项目的数据库变动比较频繁, 为了减少数据层的负担, 打算采用.net的MVC框架, 使用LINQ对付数据层.       这个框架的web.config文件里出现了configSectio ...

  9. JAVA的循环控制与循环嵌套

    循环控制和循环嵌套 循环控制是除了循环条件之外,控制循环是否进行的一个机制,这给处理循环问题带来了灵活性.循环体内的语句块可以是顺序执行的语句,可以是分支结构的语句,也可以是循环语句,循环中含循环,就 ...

  10. 22.C++- 继承与组合,protected访问级别

    在C++里,通过继承和组合实现了代码复用,使得开发效率提高,并且能够通过代码看到事物的关系 组合比继承简单,所以在写代码时先考虑能否组合,再来考虑继承. 组合的特点 将其它类的对象作为当前类的成员使用 ...