【问题标题】:How do I move an icon (drag & drop) inside a JLabel to another JLabel instead of copying it?如何将 JLabel 中的图标(拖放)移动到另一个 JLabel 而不是复制它?
【发布时间】: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);

【问题讨论】:

    标签: java swing user-interface drag-and-drop event-handling


    【解决方案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(...) 方法。当您将图标从一个标签拖到另一个标签时,拖动的图标将保持为“有一条线穿过它的圆圈”,直到您将鼠标悬停在可以接受传输的标签上,此时它会变为“移动”图标。跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多