0

I'm currently trying to fix a bug that was introduced by adding an arbitrary java.awt.event.MouseListener to a java.awt.Component. The following code is a reduced version of a drag and drop implementation for components:

  1. When a component gets pressed it is removed from its parent and can be dragged around the screen in a second window
  2. When the user releases the mouse, the component is added to some other component (for simplicity it gets added to its original parent again in the provided example)

Here is the code:

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;

public class DragComponentBug {


    public static void main(String[] args) {
        // set to true to enable the broken behavior
        boolean addMouseListener = false;

        // set to false to fix the broken behavior by not removing the label from its parent
        boolean removeLabel = true;

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLocation(100, 100);
                JWindow dragWindow = new JWindow();
                if (!removeLabel) {
                    JPanel panel = new JPanel();
                    panel.setBorder(BorderFactory.createLineBorder(Color.black));
                    dragWindow.add(panel);
                }
                JLabel label = new JLabel("Drag me around");
                frame.add(label);
                MouseAdapter labelListener = new MouseAdapter(){
                    @Override
                    public void mousePressed(MouseEvent e) {
                        System.err.println("mouse pressed: " + e);
                    }

                    @Override
                    public void mouseReleased(MouseEvent e) {
                        System.err.println("mouse released: " + e);
                    }

                    @Override
                    public void mouseDragged(MouseEvent e) {
                        System.err.println("mouse dragged: " + e);
                    }
                };
                if (addMouseListener) {
                    label.addMouseListener(labelListener);
                    label.addMouseMotionListener(labelListener);
                }

                Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
                    @Override
                    public void eventDispatched(AWTEvent event) {
                        MouseEvent e = (MouseEvent)event;
                        switch (e.getID()) {
                        case MouseEvent.MOUSE_PRESSED:
//                          System.out.println("mouse pressed:" + event);
                            if (removeLabel) {
                                label.setBorder(BorderFactory.createLineBorder(Color.black));
                                dragWindow.add(label);
                            }
                            dragWindow.setLocation(e.getLocationOnScreen());
                            dragWindow.setSize(label.getSize());
                            dragWindow.setVisible(true);
                            frame.repaint();
                            break;
                        case MouseEvent.MOUSE_RELEASED:
//                          System.out.println("mouse released:" + event);
                            if (removeLabel) {
                                label.setBorder(null); 
                                frame.add(label);
                            }
                            frame.repaint();
                            dragWindow.setVisible(false);
                            break;
                        case MouseEvent.MOUSE_DRAGGED:
//                          System.out.println("mouse dragged:" + event);
                            dragWindow.setLocation(e.getLocationOnScreen());
                            break;
                        default:
                            break;
                        }
                    }
                }, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
                frame.pack();
                frame.setVisible(true);
                frame.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosed(WindowEvent e) {
                        dragWindow.dispose();
                    }
                });
            }
        });
    }

}

Now for some reason, if one adds a MouseListener (or also a MouseMotionListener) of any kind to the dragged label, then the drag and drop behavior is broken and the global AWTEventListener does not receive any dragging or releasing mouse events (neither does the added listener). This can be tested by setting addMouseListener to true in the provided example.

Another important aspect here is, that this behavior somehow seems to be related to the fact, that the label gets removed from its parent after the mouse was pressed. If one does not remove the label, then the event dispatching will continue to work. This can be tested by setting removeLabel to false after addMouseListener was set to true.

Why are no more mouse events dispatched after the listener gets added to the label and how does this relate to removing the label from its parent? Is there any way to restore the intended behavior even if a listener is added to the label (including removing/adding the label during the drag operation)?

Thanks for any help

Balder
  • 8,623
  • 4
  • 39
  • 61
  • 1
    It seems that it can be dangerous to use multiple listeners. http://stackoverflow.com/questions/7613582/order-of-listeners-in-java. The link in the marked Answer is a good read. – Joe Feb 23 '16 at 15:54
  • As a general rule, with something complex, you should be using the Drag'n'Drop API, for [example](http://stackoverflow.com/questions/16478413/move-component-after-drag-and-drop/16481711#16481711), [example](http://stackoverflow.com/questions/11201734/java-how-to-drag-and-drop-jpanel-with-its-components/11443501#11443501) – MadProgrammer Feb 23 '16 at 22:02
  • @Joe thanks for the link, I will do some tests to see if it is related to some listener ordering changes after the removal of the label. – Balder Feb 24 '16 at 05:09
  • @MadProgrammer Using the Drag'n'Drop API is not easily possible in this particular application because of [Bug 4502185](http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4502185). – Balder Feb 24 '16 at 05:13
  • Maybe you should have a look at [this](http://rabbit-hole.blogspot.com.au/2006/05/my-drag-image-is-better-than-yours.html) – MadProgrammer Feb 24 '16 at 05:18
  • If that bug is really an issue, perhaps you need to supply an area around the component to act as the "draggable" area, perhaps as a wrapper component – MadProgrammer Feb 24 '16 at 05:34

0 Answers0