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

Posted

技术标签:

【中文标题】如何将 JLabel 中的图标(拖放)移动到另一个 JLabel 而不是复制它?【英文标题】:How do I move an icon (drag & drop) inside a JLabel to another JLabel instead of copying it? 【发布时间】:2020-01-15 09:37:57 【问题描述】:

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

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

我尝试在 TransferHandler 匿名类的 exportDone() 中将源 JLabel 的图标设置为 null,但如果 DnD 操作的源和目标相同,该图标将消失。如果有更多我应该覆盖/添加的方法或任何其他方式来完成相同的事情,请告诉我。

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

【问题讨论】:

【参考方案1】:

考虑documentation of TransferHandler.exportDone

数据导出后调用。如果操作为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);
    

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

【讨论】:

(1+) 我知道必须有更好的方法来防止组件自行掉落。使用setActive(...) 方法对我来说比跨方法跟踪源/目标组件更有意义。【参考方案2】:

我尝试在 TransferHandler 匿名类的 exportDone() 中将源 JLabel 的图标设置为 null,但如果 DnD 操作的源和目标相同,该图标将消失。

是的,需要这样做。

此外,通过覆盖importData(...) 方法,您可以保存“目标”组件,以便检查源/目标是否是相同的组件:

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

【讨论】:

【参考方案3】:

有人建议我像这样重写 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(...) 返回的布尔值如何,图标都会消失。有了它,如果 importData(...) 的返回值为 false,图标将保持不变。那么可以假设在调用 importData(...) 之后只调用 exportDone(...) 吗?

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

【讨论】:

它可以工作,但现在 - 我想我提出了这个建议,然后删除了它,因为它不能正常工作。我发现如果我单击并释放鼠标而不拖动到另一个组件,则图标被删除。我提供了一个新建议,适用于我的简单测试。 既然要检查的非法动作不止一个,那么让 exportDone(...) 实现保持这样并检查 importData(...) 中的动作不是更好吗?通过使用 legalMove 标志来决定在 importData(...) 函数中是返回 super.importData(...) 还是返回 false(如果标志为 false)。非常感谢您解决了我的困惑,伙计。 因为有不止一个非法动作要检查 - 这是一个更复杂的问题,因为 TransferHandler 现在需要知道你的应用程序的逻辑和你的当前状态应用。在这种情况下,我建议您覆盖 canImport(...) 方法。当您将图标从一个标签拖到另一个标签时,拖动的图标将保持为“有一条线穿过它的圆圈”,直到您将鼠标悬停在可以接受传输的标签上,此时它会变为“移动”图标。跨度>

以上是关于如何将 JLabel 中的图标(拖放)移动到另一个 JLabel 而不是复制它?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 dragover/dragenter HTML 5 拖放期间更改图标

如何将图标从 JLabel 转换为 BufferedImage?

如何将 JLabel 的文本放在其图标下

将JLabel图标转换为Java中的字节

将 jLabel 移动到 jPanel 中的不同位置(类似 Pacman 的游戏)

如何将项目从一个菜单移动到另一个菜单?