如何将 JLabel 内的图标(拖放)移动到另一个 JLabel 而不是复制它?

How do I move an icon (drag & drop) inside a JLabel to another JLabel instead of copying it?

我正在做一个国际象棋游戏的学校项目,我目前被困在棋子的 DnD 操作上。

在代码中,我在 exportAsDrag() 中传递了 TransferHandler.MOVE 参数,使其成为一个 MOVE 操作。但是,当从 JLabels 拖放图标时,TransferHandler 的行为仍然是 COPY 而不是 MOVE。

我尝试在 TransferHandler anonymous class 的 exportDone() 中将源 JLabel 的图标设置为空 class 但如果 DnD 操作的源和目标相同,该图标将消失。如果还有更多方法我应该 override/add 或任何其他方法来完成同样的事情,请告诉我。

MouseListener listener = new MouseAdapter()
{
    @Override
    public void mousePressed(MouseEvent e)
    {
          ChessTiles c = (ChessTiles) e.getSource();
          TransferHandler handler = c.getTransferHandler();
          handler.exportAsDrag(c, e, TransferHandler.MOVE)
    }
};

private static TransferHandler handler = new TransferHandler("icon")
{
    @Override
    public int getSourceActions (JComponent c)
    {
        return MOVE;
    }
};

tileArray[x][y].addMouseListener(listener);
tileArray[x][y].setTransferHandler(handler);

I tried setting the icon of the source JLabel to null in exportDone() in TransferHandler anonymous class but the icon will disappear if the source and destination of the DnD operation is the same.

是的,这需要完成。

此外,通过覆盖 importData(...) 方法,您可以保存 "target" 组件,这样您就可以检查 source/target 是否是同一组件:

TransferHandler iconHandler = new TransferHandler( "icon" )
{
    Component target;

    @Override
    public int getSourceActions(JComponent c)
    {
        return MOVE;
    }

    @Override
    public boolean importData(TransferSupport info)
    {
        target = info.getComponent();

        return super.importData( info );
    }

    @Override
    protected void exportDone(JComponent source, Transferable data, int action)
    {
        if (action == MOVE
        &&  source != target)
        {
            ((JLabel)source).setIcon(null);
        }
    }
};

有人建议我像这样覆盖 TransferHandler 的 exportDone(...) 函数,

@Override
    protected void exportDone(JComponent source, Transferable data, int action)
    {
        if (action == MOVE)
        {
            ((JLabel)source).setIcon(null);
        }

        //((JLabel)source).setIcon(null);
    }

如果没有 if 语句,如果我将图标设置为 null,则无论 importData(...) 返回的 bool 值如何,图标都会消失。有了它,如果 importData(...) 的返回值为 false,图标就会保留。那么可以假设在调用 importData(...) 之后只调用 exportDone(...) 吗?

它可以工作,但现在我对调用 handler.exportAsDrag(...) 后 TransferHandler 的内部函数调用顺序感到好奇。

考虑 documentation of TransferHandler.exportDone:

Invoked after data has been exported. This method should remove the data that was transferred if the action was MOVE.

这应该回答这两个问题。首先,您确实有责任实现移动语义,其次,您应该只在 action 的值为 MOVE 时执行此操作。除了不适用于您的场景的其他传输类型的可能性,因为您不支持它们,它可能会以零操作调用,以允许在中止传输后进行清理。当不满足先决条件时,这甚至可能直接从 exportAsDrag 方法发生。

如果您不想支持拖动到自身,您可以暂时禁用放置目标,使用 exportDone 方法重置 属性。

例如

public class DragAndDropExample {
    public static void main(String[] args) {
        EventQueue.invokeLater(DragAndDropExample::init);
    }

    private static void init() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch(ReflectiveOperationException|UnsupportedLookAndFeelException ex) {}
        try {
            BufferedImage img = ImageIO.read(
                new URL("https://cdn.sstatic.net/img/favicons-sprite32.png"));
            img = img.getSubimage(0, 11844, 32, 32);
            ICON = new ImageIcon(img);
        } catch(IOException ex) {
            ICON = UIManager.getIcon("OptionPane.errorIcon");
        }
        JFrame frame = new JFrame("Test");
        Container c = frame.getContentPane();

        final int gridWidth = 4, gridHeight = 4;
        c.setLayout(new GridLayout(gridHeight, gridWidth, 4, 4));
        for(int y = 0; y < gridHeight; y++) {
            for(int x = 0; x < gridWidth; x++) {
                create(x, y, c);
            }
        }

        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    static Icon ICON;

    static final MouseAdapter DRAG_INIT = new MouseAdapter() {
        @Override public void mousePressed(MouseEvent e) {
            var c = (JComponent) e.getSource();
            var handler = c.getTransferHandler();
            handler.exportAsDrag(c, e, TransferHandler.MOVE);
        }
    };
    static final TransferHandler ICON_TRANSFER = new TransferHandler( "icon" ) {
        @Override public void exportAsDrag(JComponent comp, InputEvent e, int action) {
            comp.getDropTarget().setActive(false);
            super.exportAsDrag(comp, e, action);
        }
        @Override public int getSourceActions(JComponent c) {
            return MOVE;
        }
        @Override protected void exportDone(
                                 JComponent source, Transferable data, int action) {
            source.getDropTarget().setActive(true);
            if (action == MOVE) {
                ((JLabel)source).setIcon(null);
            }
        }
    };
    private static void create(int x, int y, Container c) {
        JLabel l = new JLabel("\u00a0");
        if(x == 0 && y == 0) l.setIcon(ICON);
        l.setBorder(BorderFactory.createLineBorder(Color.lightGray, 1));
        l.setTransferHandler(ICON_TRANSFER);
        l.addMouseListener(DRAG_INIT);
        c.add(l);
    }
}

如果你不想禁用它,你可以存储组件,像中那样检查源和目标是否相同,但你应该将记住的组件设置为nullexportDone方法中,确保没有内存泄漏。