Java - 如何拖放 JPanel 及其组件
Posted
技术标签:
【中文标题】Java - 如何拖放 JPanel 及其组件【英文标题】:Java - How to drag and drop JPanel with its components 【发布时间】:2012-06-27 10:34:11 【问题描述】:我有一个关于拖放的问题: 我可以删除标签、文本或图标。但我想拖放一个 JPanel 及其所有组件(标签、文本框等)。
我该怎么做?
【问题讨论】:
没问题。你能告诉我们你到目前为止得到了什么吗? 我按照这种方式exampledepot.com/egs/javax.swing/label_LblCp.html 将文本或图标拖放到 Jlabel 上。现在我找不到拖放 Jpanel 内容的方法。如果我有一个包含文本框和标签的 Jpanel 或此 Jpanel 包含的任何内容,如何拖动此 Jpanel 并将其放在同一表单上的另一个 Jpnael 上。提前致谢 另见Introduction to DnD。 “新”传输处理程序 API 的限制可能过于严格,尽管它确实让内置组件的生活变得更加轻松 【参考方案1】:此解决方案有效。一些注意事项。
我没有使用 TransferHandler API。我不喜欢它,它太严格了,但这是个人的事情(它做什么,它做得很好),所以这可能不符合你的期望。
我正在使用 BorderLayout 进行测试。如果您想使用其他布局,您将不得不尝试弄清楚这一点。 DnD 子系统确实提供有关鼠标点的信息(移动和放下时)。
那么我们需要什么:
DataFlavor。我选择这样做是因为它允许更多的限制
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);
可转让。某种包装器,用一堆 DataFlavor(在我们的例子中,只是 PanelDataFlavor)包装数据(我们的 JPanel)
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;
一个“DragGestureListener”
为此,我创建了一个简单的 DragGestureHandler,它将“JPanel”作为要拖动的内容。这允许手势处理程序成为自我管理的。
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();
好的,这就是基础知识。现在我们需要将它们连接在一起......
所以,在我要拖动的面板中,我添加了:
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 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);
最后,我们需要向相关方注册放置目标...在那些能够支持放置的容器中,您要添加
DropTarget dropTarget;
DropHandler dropHandler;
.
.
.
dropHandler = new DropHandler();
dropTarget = new DropTarget(pnlOne, DnDConstants.ACTION_MOVE, dropHandler, true);
就个人而言,我在 addNotify 中初始化并在 removeNotify 中处理
dropTarget.removeDropTargetListener(dropHandler);
关于 addNotify 的简要说明,我已连续多次调用此方法,因此您可能需要仔细检查是否尚未设置放置目标。
就是这样。
您可能还会发现以下一些感兴趣的内容
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
即使只是出于兴趣,不检查它们也是浪费。
2018 年更新
因此,在编写原始代码 4 年后,API 的工作方式似乎发生了一些变化,至少在 MacOS 下是这样,这导致了许多问题 ?。
当调用DragSource#startDrag
时,第一个DragGestureHandler
导致NullPointerException
。这似乎与将容器的 parent
引用设置为 null
(通过将其从父容器中删除)有关。
因此,相反,我修改了 dragGestureRecognized
方法以在调用 DragSource#startDrag
之后从父级中删除 panel
...
@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();
我也修改了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();
还有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);
重要的是要注意,上述这些修改可能不是必需的,但在我让操作再次工作之后它们就存在了......
我遇到的另一个问题是一堆NotSerializableException
s ?
我需要更新 DragGestureHandler
和 DropHandler
类...
public class DragGestureHandler implements DragGestureListener, DragSourceListener, Serializable
//...
public public class DropHandler implements DropTargetListener, Serializable
//...
可运行示例...
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);
除了我上面提到的更改之外,DragGestureHandler
、DropHandler
、PanelDataFlavor
和 PanelTransferable
类保持不变。所有这些类都是独立的外部类,否则会导致额外的NotSerializableException
问题
注意事项
DragGestureHandler
由被拖动的同一组件管理可能会导致所有问题,但我没有时间调查
需要注意的是,我不会提示也不会容忍以这种方式操作组件,因为这样很容易导致解决方案可能在今天有效,但明天就无效的情况。我更喜欢转移状态或数据——更稳定。
我已经尝试了十几个基于原始答案中提出的相同概念的其他示例,它们只是转移状态并且它们都可以正常工作,只是在尝试转移 Component
s 时它失败了 - 直到上述修复申请了?
【讨论】:
非常感谢这个有用的详细答案 上面给出的代码几乎可以工作。见:java forum question about this answer 很公平。我的评论太短了。让我们调查一下。在DragGestureHandler::dragGestureRecognized
方法中,变量被称为parent
,因此隐藏了实例的parent
变量。这很容易解决。当我使用给定的代码时,我得到一个java.awt.dnd.InvalidDnDOperationException: Cannot find top-level for the drag source component
。如果我将第一行(直到 parent.repaint();
放在方法的底部,则不再抛出异常。不同之处在于 new PanelTransferable(getPanel());
从中获取面板。为什么会这样?
@Albert 猜猜看,自从我写 API 后,API 发生了变化
@Albert 记住,这段代码是 4 年前写的,可能是用 Java 6 编写的【参考方案2】:
该代码对 MadProgrammer 有很大帮助。对于任何想要使用这些类,但想要从您正在拖动的面板中的按钮启动拖动的人,我只是将扩展的 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();
那么你会在创建按钮时这样做:
this.JButtonDragIt = new DragActionButton(this.JPanel_To_Drag, "button-text-here");
【讨论】:
以上是关于Java - 如何拖放 JPanel 及其组件的主要内容,如果未能解决你的问题,请参考以下文章