Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MouseMotionListener in child component disables MouseListener in parent component

I need help to understand the event propagation in Swing. I know that each event is handled by only one component. Thus, when I have a panel outside with some child panel inside and I add mouseListeners to both of them, the one of inside will be called. That's nice and that's the expected behavior.

But I don't understand the behavior in the following situation: inside registers a MouseMotionListener and outside registers a MouseListener. I expect inside to consume all MouseMotionEvents and outside to receive the MouseEvents, because there is no listener for normal MouseEvents on inside. But that's not the case, inside somehow consumes all MouseEvents not only the MouseMotionEvents.

The following code illustrates the problem:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class EventTest {
public static void main(String... args) {
    SwingUtilities.invokeLater(new Runnable(){
        @Override
        public void run() {
            JComponent inside = new JPanel(); 
            inside.setBackground(Color.red);
            inside.setPreferredSize(new Dimension(200,200));
            MouseMotionListener mm = new MouseMotionListener() {
                @Override
                public void mouseDragged(MouseEvent arg0) {
                    System.err.println("dragged");                      
                }
                @Override
                public void mouseMoved(MouseEvent arg0) {
                    System.err.println("moved");
                }
            };
            // next line disables handling of mouse clicked events in outside 
            inside.addMouseMotionListener(mm); 

            JComponent outside = new JPanel();
            outside.add(inside);
            outside.setPreferredSize(new Dimension(300,300));
            outside.addMouseListener( new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    System.err.println("clicked");
                }
            });

            JFrame frame = new JFrame();
            frame.add(outside);
            frame.pack();
            frame.setVisible(true);
        }
    });
    }
}

I could work around the problem by registering a listeners on inside for all events the parent component might be interested in and then calling dispatchEvent to forward the event to the parent.

a) can someone point me to some docs, where this behavior is described? The javadocs of MouseEvent made me think that my expectations were right. So, I need a different description to understand it.

b) is there a better solution than the one sketched above?

Thanks, Kathrin

Edit: It is still unclear, why Swing behaves this way. But as it looks, the only way to get the stuff working is to manually forward the events, I will do it.

like image 443
Kathrin Geilmann Avatar asked Aug 06 '12 15:08

Kathrin Geilmann


2 Answers

a) By design, Java mouse events "bubble up" only if there in no mouse listener on the child component.

b) You can forward events to another component, as shown here and below.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class EventTest {

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                final JComponent outside = new JPanel();
                JComponent inside = new JPanel();
                inside.setBackground(Color.red);
                inside.setPreferredSize(new Dimension(200, 200));
                inside.addMouseMotionListener(new MouseAdapter() {

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        System.err.println("dragged");
                    }

                    @Override
                    public void mouseMoved(MouseEvent e) {
                        System.err.println("moved inside");
                        outside.dispatchEvent(e);
                    }
                });

                outside.add(inside);
                outside.setPreferredSize(new Dimension(300, 300));
                outside.addMouseMotionListener(new MouseAdapter() {

                    @Override
                    public void mouseMoved(MouseEvent arg0) {
                        System.err.println("moved outside");
                    }
                });

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(outside);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }
}
like image 186
trashgod Avatar answered Nov 15 '22 22:11

trashgod


Very similar to trashgod's answer - you can use a MouseAdapter as your motion listener, and override it to forward any events you want to be handled by the parent. This should only add a minimal amount to your code.

        MouseAdapter mm = new MouseAdapter() {
            @Override
            public void mouseDragged(MouseEvent arg0) {
                System.err.println("dragged");                      
            }
            @Override
            public void mouseMoved(MouseEvent arg0) {
                System.err.println("moved");
            }
            @Override
            public void mouseClicked(MouseEvent e) {
                outside.dispatchEvent(e);
            }
        };
        // For forwarding events
        inside.addMouseListener(mm); 
        // For consuming events you care about
        inside.addMouseMotionListener(mm);

I too couldn't find any way around using the dispatchEvent(e) method. I think you're stuck with that route.

like image 42
Nick Rippe Avatar answered Nov 15 '22 20:11

Nick Rippe