3

I want to have a JPanel called mainPanel and add several components on it; Also I defined a mouseAdapter and added to my mainPanel that overrides mouseEntered and mouseExited to for example change background color of mainPanel when mouse entered it. But when mouse entered to mainPanel and entered to components I added on it (for example labels) mouseExited event is called; But I don't want this as mouse is in area of mainPanel; I want it be called just when mouse exited mainPanel area; and also want this for mouseEntered. I previously added mouseListeners to components on mainPanel but it is not a clear solution. Can anyone tell me a clear way for my purpose?

thanks your attention; Good lock

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72
sajad
  • 2,094
  • 11
  • 32
  • 52

2 Answers2

2

You want the mouseEntered and mouseExited to be called on full boundaries. This is, as you have noticed, not directly possible with the "normal" MouseListener.

The simplest way is to add the listener to all child-components of the panel:

private static void addListenerToAllComponents(JComponent c, MouseListener l) {

    c.addMouseListener(l);

    for (Component cc : c.getComponents())
        if (cc instanceof JComponent)
            addListenerToAllComponents((JComponent) cc, l);
}

Full example:

public static void main(String[] args) {

    final JFrame frame = new JFrame("Test");

    frame.add(new JLabel("Testing"), BorderLayout.NORTH);

    final JPanel panel = new JPanel(new GridLayout(2, 1));
    panel.setBackground(Color.RED);

    MouseListener l = new MouseAdapter() {
        @Override
        public void mouseEntered(MouseEvent e) {
            panel.setBackground(Color.BLUE);
        }
        @Override
        public void mouseExited(MouseEvent e) {
            panel.setBackground(Color.RED);
        }
    };

    panel.add(new JLabel("Hello"));
    panel.add(new JTextField("World!"));

    addListenerToAllComponents(panel, l);

    frame.add(panel, BorderLayout.CENTER);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(400, 300);
    frame.setVisible(true);
}

Another workaround (previous answer)...

...is to set a GlassPane and check bounds yourself:

public static void main(String[] args) {

    JFrame frame = new JFrame("Test");

    frame.add(new JLabel("Testing"), BorderLayout.NORTH);

    final JPanel panel = new JPanel(new GridLayout(2, 1));
    frame.add(panel, BorderLayout.CENTER);

    panel.add(new JLabel("Hello"));
    panel.add(new JTextField("World!"));

    class GlassPane extends JComponent {
        GlassPane(final JComponent c) {
            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {

                    Point p = SwingUtilities.convertPoint(e.getComponent(), 
                                                          e.getPoint(), 
                                                          c);

                    if (c.contains(p))
                        c.setBackground(Color.BLUE);
                    else
                        c.setBackground(Color.RED);
                }
            });

            addMouseListener(new MouseAdapter() {
                public void mouseExited(MouseEvent e) {
                    c.setBackground(Color.MAGENTA);
                }
            });
        }
    }        

    GlassPane glass = new GlassPane(panel);
    frame.setGlassPane(glass);
    glass.setVisible(true);

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(400, 300);
    frame.setVisible(true);
}

There are some stuff you need to look in to... Event redistribution is one and might be a problem in your case. Follow this example and implement an event distribution listener on the glass pane:

MouseInputListener i = new MouseInputListener() {

    private void redispatchMouseEvent(MouseEvent e) {

        Point glassPanePoint = e.getPoint();
        Container container = frame.getContentPane();

        Point containerPoint = SwingUtilities.convertPoint(
                GlassPane.this,
                glassPanePoint,
                container);

        Component component = 
            SwingUtilities.getDeepestComponentAt(
                    container,
                    containerPoint.x,
                    containerPoint.y);

        if (component != null) {

            Point componentPoint = SwingUtilities.convertPoint(
                    GlassPane.this,
                    glassPanePoint,
                    component);

            component.dispatchEvent(new MouseEvent(component,
                    e.getID(),
                    e.getWhen(),
                    e.getModifiers(),
                    componentPoint.x,
                    componentPoint.y,
                    e.getClickCount(),
                    e.isPopupTrigger()));
        }
    }

    public void mouseMoved(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mouseDragged(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mouseClicked(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mouseEntered(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mouseExited(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mousePressed(MouseEvent e) {
        redispatchMouseEvent(e);
    }
    public void mouseReleased(MouseEvent e) {
        redispatchMouseEvent(e);
    }
};

addMouseListener(i);
addMouseMotionListener(i);
dacwe
  • 43,066
  • 12
  • 116
  • 140
  • Thank you. I saw the demo and yes it works. But it is so complicated and special example. I am looking for a simple way. – sajad Aug 16 '11 at 08:42
  • @sajad: Added nother example (first) where I add the same listener for all child-components. Is it simple enough? – dacwe Aug 16 '11 at 08:58
2

Getting mouseEvents for a component and all its children is ... tricky to get right. You might consider to rely on stable (and extensively tested :-) code around. The jdk7 way of doing it is to use a JLayer (which internally registers an AWTEventListener as it has all priviledges). For earlier versions, you can use its predecessor JXLayer

kleopatra
  • 51,061
  • 28
  • 99
  • 211