在 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 中拖放节点的主要内容,如果未能解决你的问题,请参考以下文章

Javascript - 在 Internet Explorer 和 Edge 中拖放不起作用

如何获取选定的行,包括 JTree 的子行

JAVA 得到jtree 某节点下的子节点

如何在jtree中搜索特定节点并使该节点展开。?

在 JTree 中隐藏/过滤节点?

JTree的使用(总结,非常简洁)