You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows系统下Java拖拽光标无法切换问题求助

问题分析与修复方案

你的问题出在Windows平台下Java DnD机制的光标优先级处理:当调用DragSource.startDrag()指定了拖放光标后,Windows系统会优先使用这个DragSource级别的光标,直接设置Frame的光标会被覆盖,导致看不到切换效果。而MacOS的DnD实现允许组件/Frame的光标覆盖DragSource的光标,所以代码在Mac上正常。

另外,Windows下dragOver频繁调用是平台DnD实现的特性,属于正常现象,无需特殊处理。

修复步骤

1. 移除直接设置Frame光标的代码

删除所有frame.setCursor(...)的调用,包括DragGestureHandler、DragSourceHandler、DropTargetHandler中的相关代码。

2. 通过DropTargetDragEvent控制拖放光标

在DropTargetListener的dragEnter和dragOver方法中,使用DropTargetDragEvent.setCursor()方法来设置当前拖放状态的光标,这个方法会直接通知DnD系统更新光标,在Windows下能正常生效。

3. 统一光标恢复逻辑

在DragSourceListener.dragDropEnd()中,通过DragSource的setCursor()方法恢复默认光标,确保拖放结束后回到正常状态。

修改后的完整代码

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 {

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

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

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

    }

}

class DragGestureHandler implements DragGestureListener {

    private final JList list;

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

    @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...");
            // 初始拖放光标,后续由DropTarget控制切换
            ds.startDrag(
                    dge,
                    Cursor.getPredefinedCursor(Cursor.HAND_CURSOR),
                    t,
                    new DragSourceHandler());
        }

    }

}

class DragSourceHandler implements DragSourceListener {

    @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) {
        // 拖放结束后恢复默认光标
        DragSource.getDefaultDragSource().setCursor(Cursor.getDefaultCursor());
    }
}

class DropTargetHandler implements DropTargetListener {

    private final Component targetComponent;

    public DropTargetHandler(Component targetComponent) {
        this.targetComponent = targetComponent;
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        decideCursor(dtde);
        if (targetComponent 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(dtde);
    }

    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
        // 退出目标组件时恢复初始拖放光标
        if (dte instanceof DropTargetDragEvent) {
            ((DropTargetDragEvent) dte).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        }
    }

    private void decideCursor(DropTargetDragEvent dtde) {
        if (targetComponent instanceof JList) {
            System.out.println(">>>Target LIST ");
            dtde.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        } else {
            System.out.println(">>>Target NON-LIST");
            dtde.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        }
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {
        System.out.println("Dropped...");
        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 (targetComponent instanceof JPanel) {
                            JPanel p = (JPanel) targetComponent;
                            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 {
        if (USER_DATA_FLAVOR.equals(flavor)) {
            return example;
        }
        throw new UnsupportedFlavorException(flavor);
    }

}

class Example {

    private final String name;

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

    public String getName() {
        return name;
    }

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

}

关键修改点说明

  • 移除了所有直接操作frame.setCursor()的代码,改为通过DropTargetDragEvent.setCursor()控制拖放过程中的光标,这是Java DnD API提供的标准跨平台光标控制方式。
  • 在dragExit时恢复初始的Hand光标,确保鼠标离开目标组件时回到拖放初始状态。
  • 简化了代码依赖,去掉了不必要的Frame引用传递。

内容的提问来源于stack exchange,提问作者mstoreysmith

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 04:18:29