简体   繁体   中英

Java drag cursor not changing under Windows

I'm trying to implement Drag and Drop in a desktop Java app, but although the app works fine under MacOS, I seem unable to get it to work right under Windows.

Specifically, I want the cursor to change depending on what component the mouse is hovering over. For now, I'm using predefined cursors (Hand, Crosshair) but eventually I'd like to use custom cursors. But try as I might, I can't get the Hand cursor to change to Crosshair under Windows. The same code works fine in MacOS. I've tried different Java versions (15 & 17) - no change. The other difference I'd note between MacOS and Windows: I see many more calls to dragOver in Windows, including when I'm not even moving the mouse. But the cursor doesn't change.

Can anybody tell me what I'm doing wrong?

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class DNDExample {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            }

            JFrame frame = new JFrame("Test");
            frame.setMinimumSize(new Dimension(300, 200));
            frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new BorderLayout());
            frame.add(new TestPane(frame));
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

}

class TestPane extends JPanel {

    private final JList list;
    private final JFrame frame;

    public TestPane(JFrame frame) {
        this.frame = frame;
        setLayout(new BorderLayout());
        list = new JList();
        DefaultListModel model = new DefaultListModel();
        model.addElement(new Example("Fred"));
        model.addElement(new Example("John"));
        model.addElement(new Example("Mark"));
        model.addElement(new Example("Sheila"));
        model.addElement(new Example("Angela"));
        model.addElement(new Example("Monica"));
        list.setModel(model);
        add(new JScrollPane(list), BorderLayout.WEST);

        DragGestureRecognizer dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
                list,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DragGestureHandler(list, frame));

        JPanel panel = new JPanel(new GridBagLayout());
        add(panel);

        DropTarget dt = new DropTarget(
                panel,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DropTargetHandler(panel, frame),
                true);
        DropTarget dl = new DropTarget(
                list,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DropTargetHandler(list, frame),
                true);

    }

}

class DragGestureHandler implements DragGestureListener {

    private final JList list;
    private final JFrame frame;

    public DragGestureHandler(JList list, JFrame frame) {
        this.list = list;
        this.frame = frame;
    }

    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {
        Object selectedValue = list.getSelectedValue();
        if (selectedValue instanceof Example) {
            Example user = (Example) selectedValue;
            Transferable t = new ExampleTransferable(user);
            DragSource ds = dge.getDragSource();
            frame.setCursor(new Cursor(Cursor.HAND_CURSOR));
            System.out.println("Starting drag...");
            ds.startDrag(
                    dge,
                    new Cursor(Cursor.HAND_CURSOR),
                    t,
                    new DragSourceHandler(frame));
        }

    }

}

class DragSourceHandler implements DragSourceListener {

    JFrame frame;

    public DragSourceHandler(JFrame frame) {
        this.frame = frame;
    }

    @Override
    public void dragEnter(DragSourceDragEvent dsde) {
    }

    @Override
    public void dragOver(DragSourceDragEvent dsde) {
    }

    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
    }

    @Override
    public void dragExit(DragSourceEvent dse) {
    }

    @Override
    public void dragDropEnd(DragSourceDropEvent dsde) {
        frame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
}

class DropTargetHandler implements DropTargetListener {

    private final Component panel;
    private final JFrame frame;

    public DropTargetHandler(Component panel, JFrame frame) {
        this.panel = panel;
        this.frame = frame;
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        decideCursor(panel);
        if (panel instanceof JPanel && dtde.getTransferable().isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
            System.out.println("Accept...");
            dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
        } else {
            System.out.println("Reject...");
            dtde.rejectDrag();
        }
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
        decideCursor(panel);
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
        decideCursor(panel);
    }

    public void decideCursor(Component comp) {
        if (comp instanceof JList) {
            System.out.println(">>Target LIST ");
            frame.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        } else {
            System.out.println(">>Target NON-LIST");
            frame.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        }
        frame.invalidate();
        frame.repaint();
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {
        System.out.println("Dropped...");
        frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
        if (dtde.getTransferable().isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
            Transferable t = dtde.getTransferable();
            if (t.isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
                try {
                    Object transferData = t.getTransferData(ExampleTransferable.USER_DATA_FLAVOR);
                    if (transferData instanceof Example) {
                        Example example = (Example) transferData;
                        dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                        if (panel instanceof JPanel) {
                            JPanel p = (JPanel) panel;
                            p.add(new JLabel(example.getName()));
                            p.revalidate();
                            p.repaint();
                        }
                    } else {
                        dtde.rejectDrop();
                    }
                } catch (UnsupportedFlavorException | IOException ex) {
                    dtde.rejectDrop();
                }
            } else {
                dtde.rejectDrop();
            }
        }
    }

}

class ExampleTransferable implements Transferable {

    public static final DataFlavor USER_DATA_FLAVOR = new DataFlavor(Example.class, "Example");
    private final Example example;

    public ExampleTransferable(Example example) {
        this.example = example;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[]{USER_DATA_FLAVOR};
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return USER_DATA_FLAVOR.equals(flavor);
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        Object value = null;
        if (USER_DATA_FLAVOR.equals(flavor)) {
            value = example;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
        return value;
    }

}

class Example {

    private final String name;

    public Example(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

}

It turns out that when a drag operation is in progress, you can/should change the cursor using the setCursor method of DragSourceContext -- not the setCursor method of the control (JFrame), as I had been doing. MacOS didn't seem to care, but Windows provably does.

In the solution below, I'm using the DropTarget objects to determine what cursor suits, and this cursor is then implemented by all the methods of the DragSourceListener. Note also that I've subclassed JFrame so that I can store the value of the current cursor.

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class DNDExample {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
            }

            DNDFrame frame = new DNDFrame("Test");
            frame.setMinimumSize(new Dimension(300, 200));
            frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLayout(new BorderLayout());
            frame.add(new TestPane(frame));
            frame.pack();
            frame.setLocationRelativeTo(null);

            frame.setVisible(true);
        });
    }

}

class DNDFrame extends JFrame {

    public Cursor c = null;
    public DragSource ds = null;

    public DNDFrame(String s) {
        super(s);
    }
}

class TestPane extends JPanel {

    private final JList list;
    private final DNDFrame frame;

    public TestPane(DNDFrame frame) {
        this.frame = frame;
        setLayout(new BorderLayout());
        list = new JList();
        DefaultListModel model = new DefaultListModel();
        model.addElement(new Example("Fred"));
        model.addElement(new Example("John"));
        model.addElement(new Example("Mark"));
        model.addElement(new Example("Sheila"));
        model.addElement(new Example("Angela"));
        model.addElement(new Example("Monica"));
        list.setModel(model);
        add(new JScrollPane(list), BorderLayout.WEST);

        DragGestureRecognizer dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
                list,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DragGestureHandler(list, frame));

        JPanel panel = new JPanel(new GridBagLayout());
        add(panel);

        DropTarget dt = new DropTarget(
                panel,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DropTargetHandler(panel, frame),
                true);
        DropTarget dl = new DropTarget(
                list,
                DnDConstants.ACTION_COPY_OR_MOVE,
                new DropTargetHandler(list, frame),
                true);

    }

}

class DragGestureHandler implements DragGestureListener {

    private final JList list;
    private final DNDFrame frame;
    Cursor c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);

    public DragGestureHandler(JList list, DNDFrame frame) {
        this.list = list;
        this.frame = frame;
    }

    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {
        Object selectedValue = list.getSelectedValue();
        if (selectedValue instanceof Example) {
            Example user = (Example) selectedValue;
            Transferable t = new ExampleTransferable(user);
            DragSource ds = dge.getDragSource();
            System.out.println("Starting drag...");
            ds.startDrag(
                    dge,
                    new Cursor(Cursor.HAND_CURSOR),
                    t,
                    new DragSourceHandler(frame));
        }
    }

}

class DragSourceHandler implements DragSourceListener {

    private final DNDFrame frame;

    public DragSourceHandler(DNDFrame frame) {
        this.frame = frame;
    }

    @Override
    public void dragEnter(DragSourceDragEvent dsde) {
        decideCursor(dsde.getDragSourceContext());
    }

    @Override
    public void dragOver(DragSourceDragEvent dsde) {
        decideCursor(dsde.getDragSourceContext());
    }

    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
    }

    @Override
    public void dragExit(DragSourceEvent dse) {
        decideCursor(dse.getDragSourceContext());
    }

    @Override
    public void dragDropEnd(DragSourceDropEvent dsde) {
        frame.getRootPane().setCursor(null);
    }

    public void decideCursor(DragSourceContext t) {
        t.setCursor(frame.c);
    }
}

class DropTargetHandler implements DropTargetListener {

    private final Component panel;
    private final DNDFrame frame;

    public DropTargetHandler(Component panel, DNDFrame frame) {
        this.panel = panel;
        this.frame = frame;
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        decideCursor(panel);
        if (panel instanceof JPanel && dtde.getTransferable().isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
            System.out.println("Accept...");
            dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
        } else {
            System.out.println("Reject...");
            dtde.rejectDrag();
        }
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
        decideCursor(panel);
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
        decideCursor(panel);
    }

    public void decideCursor(Component comp) {
        if (comp instanceof JList) {
            frame.c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
        } else {
            frame.c = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
        }
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {
        System.out.println("Dropped...");
        frame.setCursor(null);
        if (dtde.getTransferable().isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
            Transferable t = dtde.getTransferable();
            if (t.isDataFlavorSupported(ExampleTransferable.USER_DATA_FLAVOR)) {
                try {
                    Object transferData = t.getTransferData(ExampleTransferable.USER_DATA_FLAVOR);
                    if (transferData instanceof Example) {
                        Example example = (Example) transferData;
                        dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                        if (panel instanceof JPanel) {
                            JPanel p = (JPanel) panel;
                            p.add(new JLabel(example.getName()));
                            p.revalidate();
                            p.repaint();
                        }
                    } else {
                        dtde.rejectDrop();
                    }
                } catch (UnsupportedFlavorException | IOException ex) {
                    dtde.rejectDrop();
                }
            } else {
                dtde.rejectDrop();
            }
        }
    }

}

class ExampleTransferable implements Transferable {

    public static final DataFlavor USER_DATA_FLAVOR = new DataFlavor(Example.class, "Example");
    private final Example example;

    public ExampleTransferable(Example example) {
        this.example = example;
    }

    @Override
    public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[]{USER_DATA_FLAVOR};
    }

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return USER_DATA_FLAVOR.equals(flavor);
    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        Object value = null;
        if (USER_DATA_FLAVOR.equals(flavor)) {
            value = example;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
        return value;
    }

}

class Example {

    private final String name;

    public Example(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM