在 JTree 中拖放节点
Posted
技术标签:
【中文标题】在 JTree 中拖放节点【英文标题】:Drag and Drop nodes in JTree 【发布时间】:2011-06-03 01:16:04 【问题描述】:我很难创建一个允许通过将节点拖放到 JTree 中来重新组织节点的 JTree。这似乎应该相对简单。我看过网上的例子,但我似乎无法在我自己的代码中实现它。
例如,sun 提供的this 允许在不同组件之间拖动到树中,但不能从树本身中拖动。
我还发现这个允许您将文本拖到 JTree 中,但不能拖到树中。
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.tree.*;
public class DndTree
public static void main(String args[])
Runnable runner = new Runnable()
public void run()
JFrame f = new JFrame("D-n-D JTree");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel top = new JPanel(new BorderLayout());
JLabel dragLabel = new JLabel("Drag me:");
JTextField text = new JTextField();
text.setDragEnabled(true);
top.add(dragLabel, BorderLayout.WEST);
top.add(text, BorderLayout.CENTER);
f.add(top, BorderLayout.NORTH);
final JTree tree = new JTree();
final DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
tree.setTransferHandler(new TransferHandler()
public boolean canImport(TransferHandler.TransferSupport support)
if (!support.isDataFlavorSupported(DataFlavor.stringFlavor)
|| !support.isDrop())
return false;
JTree.DropLocation dropLocation =
(JTree.DropLocation) support.getDropLocation();
return dropLocation.getPath() != null;
public boolean importData(TransferHandler.TransferSupport support)
if (!canImport(support))
return false;
JTree.DropLocation dropLocation =
(JTree.DropLocation) support.getDropLocation();
TreePath path = dropLocation.getPath();
Transferable transferable = support.getTransferable();
String transferData;
try
transferData = (String) transferable.getTransferData(
DataFlavor.stringFlavor);
catch (IOException e)
return false;
catch (UnsupportedFlavorException e)
return false;
int childIndex = dropLocation.getChildIndex();
if (childIndex == -1)
childIndex = model.getChildCount(path.getLastPathComponent());
DefaultMutableTreeNode newNode =
new DefaultMutableTreeNode(transferData);
DefaultMutableTreeNode parentNode =
(DefaultMutableTreeNode) path.getLastPathComponent();
model.insertNodeInto(newNode, parentNode, childIndex);
TreePath newPath = path.pathByAddingChild(newNode);
tree.makeVisible(newPath);
tree.scrollRectToVisible(tree.getPathBounds(newPath));
return true;
);
JScrollPane pane = new JScrollPane(tree);
f.add(pane, BorderLayout.CENTER);
JPanel bottom = new JPanel();
JLabel comboLabel = new JLabel("DropMode");
String options[] = "USE_SELECTION",
"ON", "INSERT", "ON_OR_INSERT"
;
final DropMode mode[] = DropMode.USE_SELECTION,
DropMode.ON, DropMode.INSERT, DropMode.ON_OR_INSERT;
final JComboBox combo = new JComboBox(options);
combo.addActionListener(new ActionListener()
public void actionPerformed(ActionEvent e)
int selectedIndex = combo.getSelectedIndex();
tree.setDropMode(mode[selectedIndex]);
);
bottom.add(comboLabel);
bottom.add(combo);
f.add(bottom, BorderLayout.SOUTH);
f.setSize(300, 400);
f.setVisible(true);
;
EventQueue.invokeLater(runner);
任何参考或建议都会很棒。谢谢
【问题讨论】:
【参考方案1】:以前没有这样做过,但是快速的谷歌搜索在这里发现了同样的问题:http://www.coderanch.com/t/346509/GUI/java/JTree-drag-drop-inside-one 它有一个你可以查看的工作实现。
这是 Craig Wood 发布的相关代码:
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)
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]);
public int getSourceActions(JComponent c)
return COPY_OR_MOVE;
public boolean importData(TransferHandler.TransferSupport support)
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++)
model.insertNodeInto(nodes[i], parent, index++);
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);
【讨论】:
在线程中进一步有人指出了一个错误(在您发布答案之后)。copy(TreeNode)
方法应该是 private DefaultMutableTreeNode copy(TreeNode node) return node.clone();
有点问题。我们不能在叶子的最后一个节点之后移动一个节点。
后面的帖子指出Paul贴的方法没有编译通过,应该是:private DefaultMutableTreeNode copy(TreeNode node) DefaultMutableTreeNode n=(DefaultMutableTreeNode)node; return (DefaultMutableTreeNode) n.clone();
我不同意应该调用 node.clone。它已经通过将 Cloneable 接口传递给构造函数来使用它。也许他们使用的是较旧的 Java 版本?【参考方案2】:
@jzd 提到的 Craig Wood 的原始代码包含一个错误,其功能可以改进。我为它创建了一个 repo 并解释了所有内容:https://gitlab.com/alberthendriks/jtree-drag-drop。我改进的代码是:
/**
* Edited from https://coderanch.com/t/346509/java/JTree-drag-drop-tree-Java
* by Craig Wood and mentioned on
* https://***.com/questions/4588109/drag-and-drop-nodes-in-jtree
*/
import java.awt.datatransfer.*;
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;
DefaultMutableTreeNode treeNode =
(DefaultMutableTreeNode)tree.getPathForRow(selRows[i]).getLastPathComponent();
for (TreeNode offspring: Collections.list(treeNode.depthFirstEnumeration()))
if (tree.getRowForPath(new TreePath(((DefaultMutableTreeNode)offspring).getPath())) == dropRow)
return false;
return true;
protected Transferable createTransferable(JComponent c)
JTree tree = (JTree) c;
TreePath[] paths = tree.getSelectionPaths();
if (paths == null)
return 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 firstNode =
(DefaultMutableTreeNode) paths[0].getLastPathComponent();
HashSet<TreeNode> doneItems = new LinkedHashSet<>(paths.length);
DefaultMutableTreeNode copy = copy(firstNode, doneItems, tree);
copies.add(copy);
toRemove.add(firstNode);
for (int i = 1; i < paths.length; i++)
DefaultMutableTreeNode next =
(DefaultMutableTreeNode) paths[i].getLastPathComponent();
if (doneItems.contains(next))
continue;
// Do not allow higher level nodes to be added to list.
if (next.getLevel() < firstNode.getLevel())
break;
else if (next.getLevel() > firstNode.getLevel()) // child node
copy.add(copy(next, doneItems, tree));
// node already contains child
else // sibling
copies.add(copy(next, doneItems, tree));
toRemove.add(next);
doneItems.add(next);
DefaultMutableTreeNode[] nodes =
copies.toArray(new DefaultMutableTreeNode[copies.size()]);
nodesToRemove =
toRemove.toArray(new DefaultMutableTreeNode[toRemove.size()]);
return new NodesTransferable(nodes);
private DefaultMutableTreeNode copy(DefaultMutableTreeNode node, HashSet<TreeNode> doneItems, JTree tree)
DefaultMutableTreeNode copy = new DefaultMutableTreeNode(node);
doneItems.add(node);
for (int i=0; i<node.getChildCount(); i++)
copy.add(copy((DefaultMutableTreeNode)((TreeNode)node).getChildAt(i), doneItems, tree));
int row = tree.getRowForPath(new TreePath(copy.getPath()));
tree.expandRow(row);
return copy;
protected void exportDone(JComponent source, Transferable data, int action)
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]);
public int getSourceActions(JComponent c)
return COPY_OR_MOVE;
public boolean importData(TransferHandler.TransferSupport support)
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++)
model.insertNodeInto(nodes[i], parent, index++);
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);
【讨论】:
以上是关于在 JTree 中拖放节点的主要内容,如果未能解决你的问题,请参考以下文章