ドラッグ アンド ドロップについて質問があります。ラベル、テキスト、またはアイコンをドロップできます。しかし、JPanel とそのすべてのコンポーネント (ラベル、テキストボックスなど) をドラッグ アンド ドロップしたいと考えています。
これどうやってするの ?
ドラッグ アンド ドロップについて質問があります。ラベル、テキスト、またはアイコンをドロップできます。しかし、JPanel とそのすべてのコンポーネント (ラベル、テキストボックスなど) をドラッグ アンド ドロップしたいと考えています。
これどうやってするの ?
このソリューションは機能します。最初にいくつかの警告があります。
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);
}
}
譲渡可能。データ (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;
}
}
「DragGestureListener」
このために、ドラッグするコンテンツとして「JPanel」を受け取る単純な DragGestureHandler を作成しました。これにより、ジェスチャ ハンドラーを自己管理できるようになります。
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
興味本位でもチェックしないのはもったいない。
そのため、元のコードが作成されてから 4 年が経過した後、少なくとも MacOS では API の動作にいくつかの変更が加えられたようで、多くの問題が発生しています。
最初に呼び出されたときDragGestureHandler
に発生していました。これは、コンテナの参照を設定することに関連しているようです(親コンテナから削除することにより)。NullPointerException
DragSource#startDrag
parent
null
そのため、代わりに、親からdragGestureRecognized
削除するようにメソッドを変更したpanel
AFTERDragSource#startDrag
が呼び出されました...
@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
失敗しました-上記の修正が適用されるまで
そのコードは、MadProgrammer にとって大きな助けになります。これらのクラスを使用したいが、ドラッグしているパネルのボタンからドラッグを開始したい人のために、拡張された JPanel を、コンストラクターでパネルを受け取る 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");