使用 JTree 和模式对话框进行 Java 拖放
我想在文件系统支持的 JTree 上实现拖放,以允许用户轻松移动和复制文件。当用户尝试将文件移动或复制到已包含同名文件的目录时,我想显示一个确认对话框。
java的拖放实现在这方面存在明显的缺陷(或者可能是我对它的理解存在缺陷)。在 dnd 期间显示模式对话框会导致调用 TransferHandler 的 exportDone 方法,同时我仍在确认 importData 方法内部的放置。
我附上了一个示例应用程序来说明该问题。它不依赖于文件系统支持的 JTree,并且为了简单起见,它需要对每次删除进行确认。如果拖放 1 个元素,您会注意到它被复制到目的地,但没有从原始位置删除。如果拖放 2 个元素,您可以清楚地看到在 importData 完成之前调用了 exportDone。
如何在 Java 中实现可靠的拖放,并在必要时对每个元素进行用户确认?
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.tree.*;
public class TreeDragAndDrop {
private JScrollPane getContent() {
JTree tree = new JTree();
tree.setDragEnabled(true);
tree.setDropMode(DropMode.ON_OR_INSERT);
tree.setTransferHandler(new TreeTransferHandler());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
expandTree(tree);
return new JScrollPane(tree);
}
private void expandTree(JTree tree) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
Enumeration e = root.breadthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
if (node.isLeaf())
continue;
int row = tree.getRowForPath(new TreePath(node.getPath()));
tree.expandRow(row);
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TreeDragAndDrop().getContent());
f.setSize(400, 400);
f.setLocation(200, 200);
f.setVisible(true);
}
}
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + javax.swing.tree.DefaultMutableTreeNode[].class.getName() + "\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch (ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDrop()) {
return false;
}
support.setShowDropLocation(true);
if (!support.isDataFlavorSupported(nodesFlavor)) {
return false;
}
// Do not allow a drop on the drag source selections.
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
JTree tree = (JTree) support.getComponent();
int dropRow = tree.getRowForPath(dl.getPath());
int[] selRows = tree.getSelectionRows();
for (int i = 0; i < selRows.length; i++) {
if (selRows[i] == dropRow) {
return false;
}
}
// Do not allow MOVE-action drops if a non-leaf node is
// selected unless all of its children are also selected.
int action = support.getDropAction();
if (action == MOVE) {
return haveCompleteNode(tree);
}
// Do not allow a non-leaf node to be copied to a level
// which is less than its source level.
TreePath dest = dl.getPath();
DefaultMutableTreeNode target = (DefaultMutableTreeNode) dest.getLastPathComponent();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) path.getLastPathComponent();
if (firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel()) {
return false;
}
return true;
}
private boolean haveCompleteNode(JTree tree) {
int[] selRows = tree.getSelectionRows();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode first = (DefaultMutableTreeNode) path.getLastPathComponent();
int childCount = first.getChildCount();
// first has children and no children are selected.
if (childCount > 0 && selRows.length == 1)
return false;
// first may have children.
for (int i = 1; i < selRows.length; i++) {
path = tree.getPathForRow(selRows[i]);
DefaultMutableTreeNode next = (DefaultMutableTreeNode) path.getLastPathComponent();
if (first.isNodeChild(next)) {
// Found a child of first.
if (childCount > selRows.length - 1) {
// Not all children of first are selected.
return false;
}
}
}
return true;
}
protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
TreePath[] paths = tree.getSelectionPaths();
if (paths != null) {
// Make up a node array of copies for transfer and
// another for/of the nodes that will be removed in
// exportDone after a successful drop.
List<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) paths[0].getLastPathComponent();
DefaultMutableTreeNode copy = copy(node);
copies.add(copy);
toRemove.add(node);
for (int i = 1; i < paths.length; i++) {
DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
// Do not allow higher level nodes to be added to list.
if (next.getLevel() < node.getLevel()) {
break;
} else if (next.getLevel() > node.getLevel()) { // child node
copy.add(copy(next));
// node already contains child
} else { // sibling
copies.add(copy(next));
toRemove.add(next);
}
}
DefaultMutableTreeNode[] nodes = copies.toArray(new DefaultMutableTreeNode[copies.size()]);
nodesToRemove = toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]);
return new NodesTransferable(nodes);
}
return null;
}
/** Defensive copy used in createTransferable. */
private DefaultMutableTreeNode copy(TreeNode node) {
return new DefaultMutableTreeNode(node);
}
protected void exportDone(JComponent source, Transferable data, int action) {
System.out.println("exportDone started");
if ((action & MOVE) == MOVE) {
JTree tree = (JTree) source;
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Remove nodes saved in nodesToRemove in createTransferable.
for (int i = 0; i < nodesToRemove.length; i++) {
model.removeNodeFromParent(nodesToRemove[i]);
}
}
System.out.println("exportDone finished");
}
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
public boolean importData(TransferHandler.TransferSupport support) {
System.out.println("importData started");
if (!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dest.getLastPathComponent();
JTree tree = (JTree) support.getComponent();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for (int i = 0; i < nodes.length; i++) {
if (JOptionPane.showConfirmDialog(null, "Confirm drop of " + nodes[i].toString(), "Confirm Drop", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
model.insertNodeInto(nodes[i], parent, index++);
}
}
System.out.println("importData finished");
return true;
}
public String toString() {
return getClass().getName();
}
public class NodesTransferable implements Transferable {
DefaultMutableTreeNode[] nodes;
public NodesTransferable(DefaultMutableTreeNode[] nodes) {
this.nodes = nodes;
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (!isDataFlavorSupported(flavor))
throw new UnsupportedFlavorException(flavor);
return nodes;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return nodesFlavor.equals(flavor);
}
}
}
I would like to implement drag and drop on a file-system backed JTree to allow users to easily move and copy files. I would like to show a confirmation dialog when users attempt to move or copy a file to a directory that already contains a file with the same name.
There is an apparent flaw with java's implementation of drag and drop in this regard (or perhaps a flaw in my understanding of it). The showing of a modal dialog during a dnd causes my TransferHandler's exportDone method to be called while I am still confirming the drop inside of the importData method.
I have attached a sample application that illustrates the problem. It doesnt rely on a file system-backed JTree and it requires confirmation for every drop for simplicity. If you drag n drop 1 element, you will notice that it is copied to its destination but not removed from its original location. If you drag n drop 2 elements you can clearly see that exportDone is called before importData has finished.
How can I implement reliable drag n drop in Java with user confirmation for each element when necessary?
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.tree.*;
public class TreeDragAndDrop {
private JScrollPane getContent() {
JTree tree = new JTree();
tree.setDragEnabled(true);
tree.setDropMode(DropMode.ON_OR_INSERT);
tree.setTransferHandler(new TreeTransferHandler());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
expandTree(tree);
return new JScrollPane(tree);
}
private void expandTree(JTree tree) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
Enumeration e = root.breadthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.nextElement();
if (node.isLeaf())
continue;
int row = tree.getRowForPath(new TreePath(node.getPath()));
tree.expandRow(row);
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new TreeDragAndDrop().getContent());
f.setSize(400, 400);
f.setLocation(200, 200);
f.setVisible(true);
}
}
class TreeTransferHandler extends TransferHandler {
DataFlavor nodesFlavor;
DataFlavor[] flavors = new DataFlavor[1];
DefaultMutableTreeNode[] nodesToRemove;
public TreeTransferHandler() {
try {
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + javax.swing.tree.DefaultMutableTreeNode[].class.getName() + "\"";
nodesFlavor = new DataFlavor(mimeType);
flavors[0] = nodesFlavor;
} catch (ClassNotFoundException e) {
System.out.println("ClassNotFound: " + e.getMessage());
}
}
public boolean canImport(TransferHandler.TransferSupport support) {
if (!support.isDrop()) {
return false;
}
support.setShowDropLocation(true);
if (!support.isDataFlavorSupported(nodesFlavor)) {
return false;
}
// Do not allow a drop on the drag source selections.
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
JTree tree = (JTree) support.getComponent();
int dropRow = tree.getRowForPath(dl.getPath());
int[] selRows = tree.getSelectionRows();
for (int i = 0; i < selRows.length; i++) {
if (selRows[i] == dropRow) {
return false;
}
}
// Do not allow MOVE-action drops if a non-leaf node is
// selected unless all of its children are also selected.
int action = support.getDropAction();
if (action == MOVE) {
return haveCompleteNode(tree);
}
// Do not allow a non-leaf node to be copied to a level
// which is less than its source level.
TreePath dest = dl.getPath();
DefaultMutableTreeNode target = (DefaultMutableTreeNode) dest.getLastPathComponent();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode) path.getLastPathComponent();
if (firstNode.getChildCount() > 0 && target.getLevel() < firstNode.getLevel()) {
return false;
}
return true;
}
private boolean haveCompleteNode(JTree tree) {
int[] selRows = tree.getSelectionRows();
TreePath path = tree.getPathForRow(selRows[0]);
DefaultMutableTreeNode first = (DefaultMutableTreeNode) path.getLastPathComponent();
int childCount = first.getChildCount();
// first has children and no children are selected.
if (childCount > 0 && selRows.length == 1)
return false;
// first may have children.
for (int i = 1; i < selRows.length; i++) {
path = tree.getPathForRow(selRows[i]);
DefaultMutableTreeNode next = (DefaultMutableTreeNode) path.getLastPathComponent();
if (first.isNodeChild(next)) {
// Found a child of first.
if (childCount > selRows.length - 1) {
// Not all children of first are selected.
return false;
}
}
}
return true;
}
protected Transferable createTransferable(JComponent c) {
JTree tree = (JTree) c;
TreePath[] paths = tree.getSelectionPaths();
if (paths != null) {
// Make up a node array of copies for transfer and
// another for/of the nodes that will be removed in
// exportDone after a successful drop.
List<DefaultMutableTreeNode> copies = new ArrayList<DefaultMutableTreeNode>();
List<DefaultMutableTreeNode> toRemove = new ArrayList<DefaultMutableTreeNode>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) paths[0].getLastPathComponent();
DefaultMutableTreeNode copy = copy(node);
copies.add(copy);
toRemove.add(node);
for (int i = 1; i < paths.length; i++) {
DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
// Do not allow higher level nodes to be added to list.
if (next.getLevel() < node.getLevel()) {
break;
} else if (next.getLevel() > node.getLevel()) { // child node
copy.add(copy(next));
// node already contains child
} else { // sibling
copies.add(copy(next));
toRemove.add(next);
}
}
DefaultMutableTreeNode[] nodes = copies.toArray(new DefaultMutableTreeNode[copies.size()]);
nodesToRemove = toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]);
return new NodesTransferable(nodes);
}
return null;
}
/** Defensive copy used in createTransferable. */
private DefaultMutableTreeNode copy(TreeNode node) {
return new DefaultMutableTreeNode(node);
}
protected void exportDone(JComponent source, Transferable data, int action) {
System.out.println("exportDone started");
if ((action & MOVE) == MOVE) {
JTree tree = (JTree) source;
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Remove nodes saved in nodesToRemove in createTransferable.
for (int i = 0; i < nodesToRemove.length; i++) {
model.removeNodeFromParent(nodesToRemove[i]);
}
}
System.out.println("exportDone finished");
}
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
public boolean importData(TransferHandler.TransferSupport support) {
System.out.println("importData started");
if (!canImport(support)) {
return false;
}
// Extract transfer data.
DefaultMutableTreeNode[] nodes = null;
try {
Transferable t = support.getTransferable();
nodes = (DefaultMutableTreeNode[]) t.getTransferData(nodesFlavor);
} catch (UnsupportedFlavorException ufe) {
System.out.println("UnsupportedFlavor: " + ufe.getMessage());
} catch (java.io.IOException ioe) {
System.out.println("I/O error: " + ioe.getMessage());
}
// Get drop location info.
JTree.DropLocation dl = (JTree.DropLocation) support.getDropLocation();
int childIndex = dl.getChildIndex();
TreePath dest = dl.getPath();
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) dest.getLastPathComponent();
JTree tree = (JTree) support.getComponent();
DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
// Configure for drop mode.
int index = childIndex; // DropMode.INSERT
if (childIndex == -1) { // DropMode.ON
index = parent.getChildCount();
}
// Add data to model.
for (int i = 0; i < nodes.length; i++) {
if (JOptionPane.showConfirmDialog(null, "Confirm drop of " + nodes[i].toString(), "Confirm Drop", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
model.insertNodeInto(nodes[i], parent, index++);
}
}
System.out.println("importData finished");
return true;
}
public String toString() {
return getClass().getName();
}
public class NodesTransferable implements Transferable {
DefaultMutableTreeNode[] nodes;
public NodesTransferable(DefaultMutableTreeNode[] nodes) {
this.nodes = nodes;
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (!isDataFlavorSupported(flavor))
throw new UnsupportedFlavorException(flavor);
return nodes;
}
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return nodesFlavor.equals(flavor);
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您只需在导入后进行删除即可。
You just have to make your remove after the import.