Java – 如何拖放JPanel及其组件

我有一个关于拖放的问题:我可以放置标签,文本或图标。 但是我想拖放一个JPanel及其所有组件(Label,Textbox,..等)。

我该怎么做?

此解决scheme工作。 一些警告开始。

我没有使用TransferHandler API。 我不喜欢它,这是限制性的,但这是一个个人的事情(它做了什么,它做得很好),所以这可能不符合你的期望。

我正在用BorderLayout进行testing。 如果你想使用其他布局,你将不得不尝试解决这个问题。 DnD子系统提供关于鼠标点的信息(在移动和放下时)

那么我们需要什么:

一个DataFlavor。 我select这样做是因为它允许更多的限制

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

可转让。 某种包装数据(我们的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 over kill, 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; } } 

“DragGestureListener”

为此,我创build了一个简单的DragGestureHandler,将“JPanel”作为要拖动的内容。 这使手势处理程序成为自我pipe理。

 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 over come 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 sucessful, we need to // return the component back to it's previous // parent if (!dsde.getDropSuccess()) { getParent().add(getPanel()); getParent().invalidate(); getParent().repaint(); } } } 

好的,这是基本的。 现在我们需要把它们连接起来

所以,在面板我想拖我补充说:

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

以这种方式使用添加/删除通知的原因是保持系统清洁。 它有助于防止事件在我们不再需要时传递给我们的组件。 它还提供自动注册。 你可能希望使用自己的“setDraggable”方法。

这是拖累的一面,现在是放弃的一面。

首先,我们需要一个DropTargetListener:

 public class DropHandler implements DropTargetListener { @Override public void dragEnter(DropTargetDragEvent dtde) { // Determine if can actual process the contents comming 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); } } 

最后,我们需要注册放置目标与感兴趣的各方…在这些容器能够支持你想添加的下降

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

就我个人而言,我在addNotify中初始化并configurationremoveNotify

 dropTarget.removeDropTargetListener(dropHandler); 

关于addNotifty的一个简单说明,我已经连续多次调用这个函数,所以你可能要仔细检查你是否已经设置了放置目标。

而已。

您也可能会发现以下的一些兴趣

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/04/smooth-jlist-drop-target-animation.html

即使只是出于兴趣,也不要去检查它们。

该代码是一个巨大的帮助MadProgrammer。 对于任何人想要使用这些类,但想要从拖动的面板中的button启动拖动,我简单地将扩展的JPanelreplace为一个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(); } } 

那么你在创buildbutton时会这样做:

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