Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JLabel ToolTip interferes with MouseListener

I have Java Swing application ToolTipMouseTest

The critical line is label.setToolTipText("label" + i);. Once it is commented out very click on a label produces 2 mousePressed in console. With this line enabled click on labels would produce nothing.

Is this expected behaviour or a bug? My goal is to show tooltips without disabling MouseListener from working.

Almost SSCCE, but without imports:

public class ToolTipMouseTest {

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            new ToolTipMouseTest();
        }
    });
}

public ToolTipMouseTest() {
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLayout(new BorderLayout());

    JLayeredPane lpane = new JLayeredPane() {
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(600,400);
        }
    };

    MouseAdapter1 mouseAdapter1 = new MouseAdapter1();
    lpane.addMouseListener(mouseAdapter1);

    frame.add(lpane);

    JPanel panel1 = new JPanel();
    panel1.setSize(new Dimension(600, 400));
    panel1.setOpaque(false);

    lpane.add(panel1, JLayeredPane.PALETTE_LAYER);

    JPanel panel2 = new JPanel();
    for (int i = 0; i < 5; i++) {
        JLabel label = new JLabel("Label " + i);
        panel2.add(label);
        label.setToolTipText("label" + i); //HERE!!
    }

    JScrollPane spane = new JScrollPane(panel2) {
        private static final long serialVersionUID = 1L;

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(300, 200);
        }
    };

    MouseAdapter2 mouseAdapter2 = new MouseAdapter2();
    spane.addMouseListener(mouseAdapter2);

    panel1.add(spane);

    frame.pack();
    frame.setVisible(true);
}

private class MouseAdapter1 extends MouseAdapter {
    @Override
    public void mousePressed (MouseEvent me) {
        System.out.println("1 mousePressed");
    }
}

private class MouseAdapter2 extends MouseAdapter {
    @Override
    public void mousePressed (MouseEvent me) {
        System.out.println("2 mousePressed");
    }
}
}
like image 422
Nikolay Kuznetsov Avatar asked Feb 18 '13 07:02

Nikolay Kuznetsov


1 Answers

It is working as intended. Let me explain why.

  • When a component has no listener, the mouse events will be propagated.

  • When any component has at least one MouseListener set on it - it will consume any mouse enter/exit/click/press/release events from going down in the components hierarchy.

    It's the same for any listener such as MouseMotionListener with mouse dragged/moved.

  • When you are adding a tooltip to a component (JLabel in your case), the component automatically receive a new MouseListener and a MouseMotionListener from ToolTipManager. The registerComponent method from ToolTipManager class do this (it's invoked by setToolTipText) :

    public void registerComponent(JComponent component) {
        component.removeMouseListener(this);
        component.addMouseListener(this);
        component.removeMouseMotionListener(moveBeforeEnterListener);
        component.addMouseMotionListener(moveBeforeEnterListener);
        component.removeKeyListener(accessibilityKeyListener);
        component.addKeyListener(accessibilityKeyListener);
    }
    

In your case - JLabels are consuming mouse events and mouse motion events, and as such prevents from propagating the events to the JLayeredPane because ToolTipManager listener added itself when the tooltip is set (setToolTipText) on the component.

In order to work around this, register a listener that will pass events down. You can add that listener to every component with a tooltip that should pass mouse events down (e.g to a JLayeredPane, a JScrollPane, etc).

Here is a small example of how that could be done:

var destinationComponent = // the JLayeredPane, JScrollPane, etc with mouse listeners

componentWithToolTip.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent event) {
        destinationComponent.dispatchEvent(
                SwingUtilities.convertMouseEvent(
                        event.getComponent(), // the component with the tooltip 
                        event,
                        destinationComponent
                )
        );
    }

    // implements other mouse* handlers as required.
});

In that setup componentWithToolTip will have 2 listeners, the one from the ToolTipManager and the propagating one. When componentWithToolTip all its listeners will be triggered, and the propagating listener will dispatch to the declared destination component destinationComponent. So that destinationComponent listeners receive the mouse events as well.

like image 165
Mikle Garin Avatar answered Oct 23 '22 22:10

Mikle Garin