Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intercepting or delegating events with overlapping components

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:

enter image description here

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).

like image 272
rtheunissen Avatar asked Feb 22 '23 22:02

rtheunissen


1 Answers

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);
like image 183
kleopatra Avatar answered Apr 26 '23 23:04

kleopatra