I have two JPanels
equal in size, one over the top of the other. The top layer serves as a drag selection panel, and the other one has other components added to it. My problem is that the mouse event handlers of these added components aren't triggered, because they are handled by the overlaying panel instead. How can I still drag over the top of these added components, but still have mouseEntered
and mouseExited
enabled for the underlaying components?
Here is a screenshot:
As you can see, the selection rectangle is painted on the overlaying JPanel
, but it's as if my mouse can't get through this panel to see what's underneath (in search of a better way to explain that).
Don't use an overlaying panel. I gave a suggestion in your last posting on how you might do this.
Or, if you do use an overlaying panel, then just use it for drawing and have the underlying panel listen for the mouse events and then invoke the repainting on the overlaying panel.
Edit:
Maybe a better approach is to use a MouseListener on the individual components and then to handle the drawing of the rectangle you can use a Global Event Listener to listen for the mousePressed, mouseDragged, mouseReleased event. You would need to check the source of each event to see if the source was the panel itself oa component on the panel. You can use SwingUtilities.isDescendingFrom(...)
to help with this second test.
An alternative to re-inventing the wheel is using JLayer (new to jdk7, available for jdk6 in the SwingLabs subproject JXLayer):
JLayer is a universal decorator for Swing components which enables you
to implement various advanced painting effects as well as receive
notifications of all AWTEvents generated within its borders
Below is a quick example - just to demonstrate its usage, logic obviously incomplete :) - of spanning a rubberband with mouseEvents
// UI which allows to span a rubberband on top of the component
public static class RubberBandUI<V extends JComponent> extends LayerUI<V> {
private JLayer<?> l;
private Rectangle rubberband;
private boolean selecting;
@Override
public void installUI(JComponent c) {
super.installUI(c);
l = (JLayer<?>) c;
// this LayerUI will receive mouse/motion events
l.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
}
@Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
// JLayer must be returned to its initial state
l.setLayerEventMask(0);
l = null;
}
@Override
public void paint(Graphics g, JComponent l) {
// this paints layer as is
super.paint(g, l);
if (rubberband == null) return;
Graphics2D g2 = (Graphics2D) g;
// custom painting is here
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke(2f));
g2.draw(rubberband);
}
// intercept events as appropriate
@Override
protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends V> l) {
super.processMouseMotionEvent(e, l);
if (e.getID() == MouseEvent.MOUSE_DRAGGED && selecting) {
Point point = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
adjustRubberband(point);
l.repaint();
}
}
@Override
protected void processMouseEvent(MouseEvent e, JLayer<? extends V> l) {
super.processMouseEvent(e, l);
if (e.getID() == MouseEvent.MOUSE_RELEASED) {
endRubberband();
}
if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getSource() == l) {
startRubberband(e.getPoint());
}
}
// logic to start/stop/adjust the rubberband
private void adjustRubberband(Point point) {
// logic to span the rubberband
int width = point.x - rubberband.x;
int height = point.y - rubberband.y;
rubberband.setSize(width, height);
}
private void startRubberband(Point p) {
rubberband = new Rectangle(p);
selecting = true;
// block events to child components while drawing
l.getGlassPane().setVisible(true);
l.repaint();
}
private void endRubberband() {
selecting = false;
l.getGlassPane().setVisible(false);
l.repaint();
}
public void clear() {
rubberband = null;
l.repaint();
}
}
Sample usage snippet:
JPanel panel = new JPanel();
for (int i = 0; i < 3; i++) {
panel.add(new JButton("JButton"));
panel.add(new JCheckBox("JCheckBox"));
panel.add(new JTextField("JTextField"));
}
JLayer<JComponent> l = new JLayer<JComponent>(panel, new RubberBandUI<JComponent>());
frame.add(l);