简体   繁体   English

Java - 如何使用其组件拖放JPanel

[英]Java - How to drag and drop JPanel with its components

I have a question about dragging and droping: I can drop labels, text or icon. 我有一个关于拖放的问题:我可以删除标签,文字或图标。 But I want to drag and drop a JPanel with all its components (Label, Textbox,..etc). 但我想拖放一个JPanel及其所有组件(Label,Textbox,..等)。

How can I do this ? 我怎样才能做到这一点 ?

This solution works. 此解决方案有效。 Some cavets to start with. 一些警告开始。

I didn't use the TransferHandler API. 我没有使用TransferHandler API。 I don't like it, it's too restrictive, but that's a personal thing (what it does, it does well), so this might not meet your expectations. 我不喜欢它,它太限制了,但这是个人的事情(它做了什么,它做得很好),所以这可能不符合你的期望。

I was testing with BorderLayout. 我正在使用BorderLayout进行测试。 If you want to use other layouts, you're going to have to try and figure that out. 如果你想使用其他布局,你将不得不尝试解决这个问题。 The DnD subsystem does provide information about the mouse point (when moving and dropping). DnD子系统确实提供有关鼠标点的信息(移动和丢弃时)。

So what do we need: 那么我们需要什么:

A DataFlavor. 一个DataFlavor。 I chose to do this because it allows a greater deal of restriction 我选择这样做是因为它允许更多的限制

public class PanelDataFlavor extends DataFlavor {

    // This saves me having to make lots of copies of the same thing
    public static final PanelDataFlavor SHARED_INSTANCE = new PanelDataFlavor();

    public PanelDataFlavor() {

        super(JPanel.class, null);

    }

}

A Transferable. 可转让的。 Some kind of wrapper that wraps the data (our JPanel) up with a bunch of DataFlavors (in our case, just the PanelDataFlavor) 某种包装数据(我们的JPanel)包装了一堆DataFlavor(在我们的例子中,只是PanelDataFlavor)

public class PanelTransferable implements Transferable {

    private DataFlavor[] flavors = new DataFlavor[]{PanelDataFlavor.SHARED_INSTANCE};
    private JPanel panel;

    public PanelTransferable(JPanel panel) {
        this.panel = panel;
    }

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

    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {

        // Okay, for this example, this is overkill, but makes it easier
        // to add new flavor support by subclassing
        boolean supported = false;

        for (DataFlavor mine : getTransferDataFlavors()) {

            if (mine.equals(flavor)) {

                supported = true;
                break;

            }

        }

        return supported;

    }

    public JPanel getPanel() {

        return panel;

    }

    @Override
    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {

        Object data = null;
        if (isDataFlavorSupported(flavor)) {

            data = getPanel();

        } else {

            throw new UnsupportedFlavorException(flavor);

        }

        return data;

    }

}

A "DragGestureListener" 一个“DragGestureListener”

For this, I created a simple DragGestureHandler that takes a "JPanel" as the content to be dragged. 为此,我创建了一个简单的DragGestureHandler,它将“JPanel”作为要拖动的内容。 This allows the gesture handler to become self managed. 这允许手势处理程序变为自我管理。

public class DragGestureHandler implements DragGestureListener, DragSourceListener {

    private Container parent;
    private JPanel child;

    public DragGestureHandler(JPanel child) {

        this.child = child;

    }

    public JPanel getPanel() {
        return child;
    }

    public void setParent(Container parent) {
        this.parent = parent;
    }

    public Container getParent() {
        return parent;
    }

    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {

        // When the drag begins, we need to grab a reference to the
        // parent container so we can return it if the drop
        // is rejected
        Container parent = getPanel().getParent();

        setParent(parent);

        // Remove the panel from the parent.  If we don't do this, it
        // can cause serialization issues.  We could overcome this
        // by allowing the drop target to remove the component, but that's
        // an argument for another day
        parent.remove(getPanel());

        // Update the display
        parent.invalidate();
        parent.repaint();

        // Create our transferable wrapper
        Transferable transferable = new PanelTransferable(getPanel());

        // Start the "drag" process...
        DragSource ds = dge.getDragSource();
        ds.startDrag(dge, Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR), transferable, this);

    }

    @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) {

        // If the drop was not successful, we need to
        // return the component back to it's previous
        // parent
        if (!dsde.getDropSuccess()) {

            getParent().add(getPanel());

            getParent().invalidate();
            getParent().repaint();

        }
    }
}

Okay, so that's basics. 好的,这是基础知识。 Now we need to wire it all together... 现在我们需要将它们连接在一起......

So, in the panel I want to drag, I added: 所以,在我想拖动的面板中,我补充说:

    private DragGestureRecognizer dgr;
    private DragGestureHandler dragGestureHandler;

    @Override
    public void addNotify() {

        super.addNotify();

        if (dgr == null) {

            dragGestureHandler = new DragGestureHandler(this);
            dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
                    this,
                    DnDConstants.ACTION_MOVE,
                    dragGestureHandler);

        }

    }

    @Override
    public void removeNotify() {

        if (dgr != null) {

            dgr.removeDragGestureListener(dragGestureHandler);
            dragGestureHandler = null;

        }

        dgr = null;

        super.removeNotify();

    }

The reason for using the add/remove notify in this way is to keep the system clean. 以这种方式使用添加/删除通知的原因是为了保持系统清洁。 It helps prevent events from been delivered to our component when we no longer need them. 当我们不再需要事件时,它有助于防止事件传递到我们的组件。 It also provides automatic registration. 它还提供自动注册。 You may wish to use your own "setDraggable" method. 您可能希望使用自己的“setDraggable”方法。

That's the drag side, now for the drop side. 这是拖累方面,现在是下降方面。

First, we need a DropTargetListener: 首先,我们需要一个DropTargetListener:

public class DropHandler implements DropTargetListener {

    @Override
    public void dragEnter(DropTargetDragEvent dtde) {

        // Determine if we can actually process the contents coming in.
        // You could try and inspect the transferable as well, but 
        // there is an issue on the MacOS under some circumstances
        // where it does not actually bundle the data until you accept the
        // drop.
        if (dtde.isDataFlavorSupported(PanelDataFlavor.SHARED_INSTANCE)) {

            dtde.acceptDrag(DnDConstants.ACTION_MOVE);

        } else {

            dtde.rejectDrag();

        }

    }

    @Override
    public void dragOver(DropTargetDragEvent dtde) {
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }

    @Override
    public void dragExit(DropTargetEvent dte) {
    }

    @Override
    public void drop(DropTargetDropEvent dtde) {

        boolean success = false;

        // Basically, we want to unwrap the present...
        if (dtde.isDataFlavorSupported(PanelDataFlavor.SHARED_INSTANCE)) {

            Transferable transferable = dtde.getTransferable();
            try {

                Object data = transferable.getTransferData(PanelDataFlavor.SHARED_INSTANCE);
                if (data instanceof JPanel) {

                    JPanel panel = (JPanel) data;

                    DropTargetContext dtc = dtde.getDropTargetContext();
                    Component component = dtc.getComponent();

                    if (component instanceof JComponent) {

                        Container parent = panel.getParent();
                        if (parent != null) {

                            parent.remove(panel);

                        }

                        ((JComponent)component).add(panel);

                        success = true;
                        dtde.acceptDrop(DnDConstants.ACTION_MOVE);

                        invalidate();
                        repaint();

                    } else {

                        success = false;
                        dtde.rejectDrop();

                    }

                } else {

                    success = false;
                    dtde.rejectDrop();

                }

            } catch (Exception exp) {

                success = false;
                dtde.rejectDrop();
                exp.printStackTrace();

            }

        } else {

            success = false;
            dtde.rejectDrop();

        }

        dtde.dropComplete(success);

    }

}

Finally, we need to register the drop target with interested parties... In those containers capable of supporting the drop, you want to add 最后,我们需要向感兴趣的各方注册掉落目标......在那些能够支持掉落的容器中,你想要添加

DropTarget dropTarget;
DropHandler dropHandler;

.
.
.

dropHandler = new DropHandler();
dropTarget = new DropTarget(pnlOne, DnDConstants.ACTION_MOVE, dropHandler, true);

Personally, I initialise in the addNotify and dispose in the removeNotify 就个人而言,我在addNotify中初始化并在removeNotify中处置

dropTarget.removeDropTargetListener(dropHandler);

Just a quick note on addNotify, I have had this been called a number of times in succession, so you may want to double-check that you haven't already set up the drop targets. 只是关于addNotify的快速说明,我已连续多次调用它,因此您可能需要仔细检查您是否已经设置了放置目标。

That's it. 而已。

You may also find some of the following of interest 您可能还会发现以下一些感兴趣的内容

http://rabbit-hole.blogspot.com.au/2006/05/my-drag-image-is-better-than-yours.html http://rabbit-hole.blogspot.com.au/2006/05/my-drag-image-is-better-than-yours.html

http://rabbit-hole.blogspot.com.au/2006/08/drop-target-navigation-or-you-drag.html http://rabbit-hole.blogspot.com.au/2006/08/drop-target-navigation-or-you-drag.html

http://rabbit-hole.blogspot.com.au/2006/04/smooth-jlist-drop-target-animation.html http://rabbit-hole.blogspot.com.au/2006/04/smooth-jlist-drop-target-animation.html

It would be waste not to check them, even if just out of interest. 即使只是出于兴趣,也不会检查它们是浪费。

2018 Update 2018年更新

So, after 4 years since the original code was written, there seems to have been some changes into how the API works, at least under MacOS, which are causing a number of issues 🙄. 因此,在编写原始代码4年后,似乎对API的工作方式进行了一些更改,至少在MacOS下是如此,这导致了许多问题。

First DragGestureHandler was causing a NullPointerException when DragSource#startDrag was been called. 第一个DragGestureHandler在调用DragSource#startDrag时导致NullPointerException This seems to be related to setting the container's parent reference to null (by removing it from the parent container). 这似乎与将容器的parent引用设置为null (通过从父容器中删除它)有关。

So, instead, I modified the dragGestureRecognized method to remove the panel from the parent AFTER DragSource#startDrag was called... 所以,相反,我修改了dragGestureRecognized方法,以便从父对象中移除panel AFTER DragSource#startDrag dragGestureRecognized被调用...

@Override
public void dragGestureRecognized(DragGestureEvent dge) {
    // When the drag begins, we need to grab a reference to the
    // parent container so we can return it if the drop
    // is rejected
    Container parent = getPanel().getParent();
    System.out.println("parent = " + parent.hashCode());
    setParent(parent);

    // Remove the panel from the parent.  If we don't do this, it
    // can cause serialization issues.  We could overcome this
    // by allowing the drop target to remove the component, but that's
    // an argument for another day
    // This is causing a NullPointerException on MacOS 10.13.3/Java 8
    //      parent.remove(getPanel());
    //      // Update the display
    //      parent.invalidate();
    //      parent.repaint();

    // Create our transferable wrapper
    System.out.println("Drag " + getPanel().hashCode());
    Transferable transferable = new PanelTransferable(getPanel());
    // Start the "drag" process...
    DragSource ds = dge.getDragSource();
    ds.startDrag(dge, null, transferable, this);

    parent.remove(getPanel());
    // Update the display
    parent.invalidate();
    parent.repaint();
}

I also modified the DragGestureHandler#dragDropEnd method 我还修改了DragGestureHandler#dragDropEnd方法

@Override
public void dragDropEnd(DragSourceDropEvent dsde) {
    // If the drop was not successful, we need to
    // return the component back to it's previous
    // parent
    if (!dsde.getDropSuccess()) {
        getParent().add(getPanel());
    } else {
        getPanel().remove(getPanel());
    }
    getParent().invalidate();
    getParent().repaint();
}

And DropHandler#drop DropHandler#drop

@Override
public void drop(DropTargetDropEvent dtde) {
    boolean success = false;
    // Basically, we want to unwrap the present...
    if (dtde.isDataFlavorSupported(PanelDataFlavor.SHARED_INSTANCE)) {
        Transferable transferable = dtde.getTransferable();
        try {
            Object data = transferable.getTransferData(PanelDataFlavor.SHARED_INSTANCE);
            if (data instanceof JPanel) {
                JPanel panel = (JPanel) data;
                DropTargetContext dtc = dtde.getDropTargetContext();
                Component component = dtc.getComponent();
                if (component instanceof JComponent) {
                    Container parent = panel.getParent();
                    if (parent != null) {
                        parent.remove(panel);
                        parent.revalidate();
                        parent.repaint();
                    }
                    ((JComponent) component).add(panel);
                    success = true;
                    dtde.acceptDrop(DnDConstants.ACTION_MOVE);
                    ((JComponent) component).invalidate();
                    ((JComponent) component).repaint();
                } else {
                    success = false;
                    dtde.rejectDrop();
                }
            } else {
                success = false;
                dtde.rejectDrop();
            }
        } catch (Exception exp) {
            success = false;
            dtde.rejectDrop();
            exp.printStackTrace();
        }
    } else {
        success = false;
        dtde.rejectDrop();
    }
    dtde.dropComplete(success);
}

It's important to note that these above modifications probably aren't required, but they existed after the point I got the operations to work again... 重要的是要注意,上述修改可能不是必需的,但是在我让操作再次工作之后它们就存在了......

Another issue I came across was a bunch of NotSerializableException s 🙄 我碰到的另一个问题是一堆NotSerializableException小号🙄

I was required to update the DragGestureHandler and DropHandler classes... 我被要求更新DragGestureHandlerDropHandler类......

public class DragGestureHandler implements DragGestureListener, DragSourceListener, Serializable {
    //...
}

public public class DropHandler implements DropTargetListener, Serializable {
    //...
}

Runnable example... 可运行的例子......

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.io.Serializable;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test implements Serializable {

    public static void main(String[] args) {
        new Test();;
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new GridLayout(1, 2));

            JPanel container = new OutterPane();

            DragPane drag = new DragPane();
            container.add(drag);

            add(container);
            add(new DropPane());
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

    }

    public class OutterPane extends JPanel {

        public OutterPane() {
            setBackground(Color.GREEN);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(100, 100);
        }

    }

}

DragPane

import java.awt.Color;
import java.awt.Dimension;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import javax.swing.JPanel;

public class DragPane extends JPanel {

    private DragGestureRecognizer dgr;
    private DragGestureHandler dragGestureHandler;

    public DragPane() {
        System.out.println("DragPane = " + this.hashCode());
        setBackground(Color.RED);
        dragGestureHandler = new DragGestureHandler(this);
        dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, dragGestureHandler);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(50, 50);
    }

}

DropPane

import java.awt.Color;
import java.awt.Dimension;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import javax.swing.JPanel;

public class DropPane extends JPanel {

    DropTarget dropTarget;
    DropHandler dropHandler;

    public DropPane() {
        setBackground(Color.BLUE);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(100, 100);
    }

    @Override
    public void addNotify() {
        super.addNotify(); //To change body of generated methods, choose Tools | Templates.
        dropHandler = new DropHandler();
        dropTarget = new DropTarget(this, DnDConstants.ACTION_MOVE, dropHandler, true);
    }

    @Override
    public void removeNotify() {
        super.removeNotify(); //To change body of generated methods, choose Tools | Templates.
        dropTarget.removeDropTargetListener(dropHandler);
    }

}

The DragGestureHandler , DropHandler , PanelDataFlavor and PanelTransferable classes remain the same, except for the changes I've mentioned above. DragGestureHandlerDropHandlerPanelDataFlavorPanelTransferable类保持不变,除了我上面提到的更改。 All these classes are standalone, external classes, otherwise it causes additional NotSerializableException problems 所有这些类都是独立的外部类,否则会导致其他NotSerializableException问题

Notes 笔记

It's possible that having the DragGestureHandler managed by the same component which is been dragged could be causing the over all issues, but I don't have the time to investigate 由拖动的同一组件管理DragGestureHandler可能会导致所有问题,但我没有时间进行调查

It should be noted that, I don't prompt nor condone manipulating components in this way, as it's way to easy to end up in situations where a solution might work today, but won't work tomorrow. 应该注意的是,我不会以这种方式提示或纵容操纵组件,因为它很容易在解决方案今天可以工作但明天无法工作的情况下结束。 I prefer to transfer state or data instead - much more stable. 我更喜欢转移状态或数据 - 更稳定。

I had tried a dozen other examples based around the same concept presented in the original answer which simply transferred state and they all worked without issue, it was only when trying to transfer Component s it failed - until the above fix was applied 🙄 我已经尝试了十几个基于原始答案中提出的相同概念的其他示例,这些示例简单地转移状态并且它们都没有问题地工作,只有在尝试传输Component时才失败 - 直到上面的修复被应用🙄

That code is a HUGE help MadProgrammer. 该代码是一个巨大的帮助MadProgrammer。 For anyone wanting to use those classes, but wants to initiate the drag from a button in the panel you are dragging, I simply replaced the extended JPanel with one for a JButton that takes the panel in the constructor: 对于任何想要使用这些类但想要从您正在拖动的面板中的按钮启动拖动的人,我只需将扩展的JPanel替换为JButton的一个,该JButton接受构造函数中的面板:

public class DragActionButton  extends JButton {
  private DragGestureRecognizer dgr;
  private DragGestureHandler dragGestureHandler;
  private JPanel actionPanel;
DragActionButton (JPanel actionPanel, String buttonText)
 {
 this.setText(buttonText);
 this.actionPanel = actionPanel;
 }   

@Override
public void addNotify() {

    super.addNotify();

    if (dgr == null) {

        dragGestureHandler = new DragGestureHandler(this.actionPanel);
        dgr = DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
                this,
                DnDConstants.ACTION_MOVE,
                dragGestureHandler);

    }

}

@Override
public void removeNotify() {

    if (dgr != null) {

        dgr.removeDragGestureListener(dragGestureHandler);
        dragGestureHandler = null;

    }

    dgr = null;

    super.removeNotify();

}

}

then you'd do this when creating the button: 然后你在创建按钮时这样做:

  this.JButtonDragIt = new DragActionButton(this.JPanel_To_Drag, "button-text-here");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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