• Swing图层的应用——实现tooltip显示


    没有错是世纪前的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

      1 package;
      2 
      3 import java.awt.BorderLayout;
      4 import java.awt.Color;
      5 import java.awt.Component;
      6 import java.awt.Dimension;
      7 import java.awt.Point;
      8 import java.awt.event.ActionEvent;
      9 import java.awt.event.ActionListener;
     10 import java.awt.event.MouseAdapter;
     11 import java.awt.event.MouseEvent;
     12 
     13 import javax.swing.JComponent;
     14 import javax.swing.JLayeredPane;
     15 import javax.swing.JPanel;
     16 import javax.swing.JRootPane;
     17 import javax.swing.JTextPane;
     18 import javax.swing.SwingUtilities;
     19 import javax.swing.Timer;
     20 import javax.swing.border.LineBorder;
     21 
     22 public class PopTimingTip extends JComponent {
     23 
     24     
     25     private JPanel mainPanel;
     26     private JTextPane textComponent;
     27     private TipListener tipListener;
     28     private Component tipParent;
     29     private int initTime = 0;
     30     private int lastTime = 0;
     31     private int vanishTime = 0;
     32     private JLayeredPane windowLayer;//窗口的遮罩层,不能随便修改,只作父对象应用
     33     private Point constPoint = new Point(12, 5);//距离鼠标常量,防止鼠标遮挡
     34     private Point constPoint2 = new Point(2,2);//常量2 防止离鼠标太近,触发mouseexit事件
     35     private Timer tipTimer;
     36     private TipTimerListener tipTimerListener;
     37     private static PopTimingTip popTimingTip;
     38     private JRootPane rootPane;//当前根面板
     39     private Dimension curTipSize;
     40     private int curConType;
     41     
     42     /**
     43      * 单例外部不允许初始化
     44      */
     45     private PopTimingTip() {
     46         super();
     47         initTip();
     48     }
     49     
     50     public static PopTimingTip getInstance() {
     51         if(popTimingTip == null) {
     52             popTimingTip = new PopTimingTip();
     53         }
     54         return popTimingTip;
     55     }
     56     
     57     private void initTip() {
     58         this.setLayout(new BorderLayout());
     59         this.setOpaque(false);
     60         //this.setBorder(null);
     61         this.setVisible(false);
     62         textComponent = new JTextPane();
     63         textComponent.setContentType("text/html");
     64         textComponent.setBorder(new LineBorder(Color.BLACK));
     65         textComponent.setBackground(new Color(245, 245, 245));
     66         mainPanel = new JPanel(new BorderLayout());
     67         mainPanel.add(textComponent, BorderLayout.CENTER);
     68         this.add(mainPanel, BorderLayout.CENTER);
     69         
     70         tipTimerListener = new TipTimerListener();
     71         tipTimerListener.state = 0;
     72         
     73         tipListener = new TipListener();
     74         tipTimer = new Timer(0, tipTimerListener);
     75         tipTimer.setRepeats(false);
     76         
     77         curTipSize = new Dimension(0,0);
     78     }
     79     public void showTip() {
     80         this.setVisible(true);
     81     }
     82     /**
     83      * 为某个组件设置tip
     84      * @param parent 显示tooltip的对象
     85      * @param text
     86      */
     87     public void showTipText(JComponent parent, String text) {
     88         if(parent == null) {
     89             return;
     90         }
     91         //如果进入了新的组件,先从旧组件中移除侦听防止泄漏
     92         if(tipParent != null && tipParent != parent) {
     93             tipParent.removeMouseListener(tipListener);
     94             tipParent.removeMouseMotionListener(tipListener);
     95         }
     96         tipParent = parent;
     97         
     98         rootPane = parent.getRootPane();
     99         //防止异常获取不了根面板的情况
    100         if(rootPane == null) {
    101             return;
    102         }
    103         
    104         JLayeredPane layerPane = rootPane.getLayeredPane();
    105         //先从旧面板中移除tip
    106         if(windowLayer != null && windowLayer != layerPane) {
    107             windowLayer.remove(this);
    108         }
    109         windowLayer = layerPane;
    110         //防止还有没有移除侦听的组件
    111         tipParent.removeMouseListener(tipListener);
    112         tipParent.removeMouseMotionListener(tipListener);
    113         layerPane.remove(this);
    114         //放置tip在遮罩窗口顶层
    115         layerPane.add(this, JLayeredPane.POPUP_LAYER);
    116         //窗口遮罩层添加侦听
    117         tipParent.addMouseMotionListener(tipListener);
    118         tipParent.addMouseListener(tipListener);
    119         //测试侦听器数量
    120         //System.out.println(tipParent.getMouseListeners().length + " " + tipParent.getMouseMotionListeners().length);
    121         //设置tiptext
    122         textComponent.setText(text);
    123         mainPanel.doLayout();
    124         //this.setPreferredSize(textComponent.getPreferredSize());
    125         curTipSize = textComponent.getPreferredSize();
    126         this.setSize(textComponent.getPreferredSize().width, textComponent.getPreferredSize().height);
    127     }
    128     
    129     /**
    130      * 初始化toolTip
    131      * @param contentType 0:html  1:文本类型 
    132      * @param initTime 鼠标进入后等待时间
    133      * @param lastTime 持续时间(未完成)
    134      * @param vanishTime 鼠标移走后消失时间(未完成)
    135      */
    136     public void setConfigure(int contentType, int initTime) {
    137         if(contentType == 0 && curConType != contentType) {
    138             textComponent.setContentType("text/html");
    139         } else if(contentType ==1 && curConType != contentType) {
    140             textComponent.setContentType("text/plain");
    141         }
    142         curConType = contentType;
    143         this.initTime = initTime;
    144         //this.vanishTime = vanishTime;
    145         //this.lastTime = lastTime;
    146     }
    147     /**
    148      * 坐标转换,标签跟随鼠标移动
    149      */
    150     private void followWithMouse(MouseEvent e) {
    151         if(windowLayer == null) {
    152             return;
    153         }
    154         
    155         Point screenPoint = e.getLocationOnScreen();
    156         
    157         SwingUtilities.convertPointFromScreen(screenPoint, windowLayer);
    158         
    159         int newLocationX = screenPoint.x + constPoint.x;
    160         int newLocationY = screenPoint.y + constPoint.y;
    161         
    162         Dimension tipSize = textComponent.getPreferredSize();
    163         if(newLocationX + tipSize.width > rootPane.getWidth()) {
    164             newLocationX = screenPoint.x - tipSize.width - constPoint2.x;
    165         }
    166         if(newLocationY + tipSize.height > rootPane.getHeight()) {
    167             newLocationY = screenPoint.y - tipSize.height - constPoint2.y;
    168         }
    169         this.setLocation(newLocationX, newLocationY);
    170         //textComponent.getPreferredSize()在html初始化计算的时候有问题,重算一次
    171         if(!curTipSize.equals(textComponent.getPreferredSize())) {
    172             this.setSize(textComponent.getPreferredSize().width, textComponent.getPreferredSize().height);
    173         }
    174     }
    175     
    176     private void setTipState(int state) {
    177         tipTimer.stop();//停止上一次的任务
    178         if(state == 0) {//进入组件,延迟显示
    179             tipTimerListener.state = 0;
    180             tipTimer.setInitialDelay(initTime);
    181             tipTimer.start();
    182         } else if(state == 1) {//鼠标移出,组件消失
    183             tipTimerListener.state = 1;
    184             PopTimingTip.this.setVisible(false);
    185         }
    186     }
    187     
    188     private class TipTimerListener implements ActionListener {
    189         int state;
    190         public void actionPerformed(ActionEvent e) {
    191             if(state == 0) {
    192                 PopTimingTip.this.setVisible(true);
    193             }
    194         }
    195     }
    196     
    197     /**
    198      * 鼠标移除后及时清除侦听防止侦听器溢出
    199      */
    200     private void removeTipAndListener() {
    201         if(tipParent == null) {
    202             return;
    203         }
    204         tipParent.removeMouseListener(tipListener);
    205         tipParent.removeMouseMotionListener(tipListener);
    206         if(windowLayer != null) {
    207             windowLayer.remove(this);
    208         }
    209     }
    210     
    211     private class TipListener extends MouseAdapter {
    212         public void mouseEntered(MouseEvent e) {
    213             setTipState(0);
    214             followWithMouse(e);
    215         }
    216         
    217         /**
    218          * 鼠标移出对象时,移除对象的侦听和ToolTip
    219          */
    220         public void mouseExited(MouseEvent e) {
    221             setTipState(1);
    222             followWithMouse(e);
    223             removeTipAndListener();
    224         }
    225         
    226         //在组件上移动时触发
    227         public void mouseMoved(MouseEvent e){
    228             setTipState(0);
    229             followWithMouse(e);
    230         }
    231         
    232         public void mouseClicked(MouseEvent e) {
    233             if((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {//右键点击,tip消失
    234                 setTipState(1);
    235                 followWithMouse(e);
    236                 removeTipAndListener();
    237             }
    238         }
    239     }
    240 
    241 }

    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("当前进入的按钮是:
    ")
                .append(btn.getName()).append("
    ")
                .append("正在进行演示自定义Tooltip!
    ")
                .append("请自行查看源码");
                
                PopTimingTip.getInstance().setConfigure(1, 300);
                PopTimingTip.getInstance().showTipText(btn, sb.toString());
            }
        }
    
    }

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

    遇到边界平移

    总结

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

  • 相关阅读:
    Datatables 在asp.net mvc
    VS Code开发调试.NET Core
    领域模型中的用户设计
    MVC数组模型绑定
    Java RMI(远程方法调用) 实例与分析 (转)
    oracle转mysql总结(转)
    Windows远程桌面连接Ubuntu 14.04 (转)
    oracle转Mysql中,varchar2(10)和number应该转换为什么类型? (转)
    MySQL与Oracle的语法区别详细对比 (转)
    python购物淫秽数据分析(2)
  • 原文地址:https://www.cnblogs.com/actionscr/p/8848540.html
Copyright © 2020-2023  润新知