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");
}
}
}
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 - JLabel
s 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.