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

Posted

技术标签:

【中文标题】如何获取选定的行,包括 JTree 的子行【英文标题】:How to get selected rows including child rows of a JTree 【发布时间】:2021-12-02 13:25:03 【问题描述】:

我正在使用TransferHandler 在 JTree 组件上实现拖放功能。我在 CodeRanch 上使用了 this 旧线程,这几乎是任何论坛上出现任何有关此问题的问题时唯一可以链接到的明显资源。

我已经让它与我的数据和 UI 一起工作,因此可以轻松拖放叶节点。当我尝试拖动有孩子的节点时,问题就来了。覆盖的布尔函数 canImport(TransferSupport support) 返回 false,因为它检查树上的选定行。因此,我随后测试了我是否可以CTRL + Click 在选择我的拖动操作的“根”节点后手动选择子节点,这工作得很好,并且该节点,包括它的所有子节点都被拖放。

我的问题是:要获取选定的行,我调用 tree.getSelectionRows() 并将该值保存到整数数组。如何将子行放入该整数数组?我知道我可能会重写我的TransferHandler 的其他部分,但由于其他一切工作正常,只要它实际选择我预期的行,我觉得这是更简单的方法。

【问题讨论】:

您应该重写您的copy 方法并不仅为当前选定的节点创建副本,还为其所有子节点创建副本。请注意,此解决方案可以在您的nodesToRemove 列表中提供重复项(当用户手动选择所选节点的子节点时)。 @SergiyMedvynskyy 也许我理解错了,但这不是通过createTransferable() 中的paths 对象进行迭代的目的吗?那里的问题似乎又是调用tree.getSelectionPaths() 只返回手动选择的路径,而不是子元素。 问题在于对haveCompleteNode 的调用,正如cmets 所说的// Do not allow MOVE-action drops if a non-leaf node is selected unless all of its children are also selected.。这对我来说有点奇怪,因为选择一个分支节点应该带着它的孩子 【参考方案1】:

所以,拖放是……复杂的。您需要注意很多问题。

在链接的示例中,您应该注意它处理的是DefaultMutableTreeNodes 而不是TreeNodes,这可能不是问题,但您可能需要牢记这一点。

haveCompleteNode 似乎会阻止你移动分支节点,如果它的子元素没有被选中......这对我来说似乎有点奇怪。

此外,由于某种原因,它后面的代码似乎阻止您将节点移动/复制到源节点当前级别之上的位置......

我也不确定我是否会保留所选节点的两个副本,这就是我,但防御性副本似乎有点过头(可能是一个特殊的边缘情况,但如果树可以在拖动操作期间更新,但这就是我)。

那么,这里的教训是什么?

您在网络上找到的任何代码都需要您花一些时间真正弄清楚它在做什么。它可能一开始很适合您的需求,但在某个时间点,您将不得不挽起袖子,深入研究以根据自己的需要对其进行修改。

您应该做的第一件事是充分了解Drag and Drop and Data Transfer API。

以下是您链接的代码的修改示例,它允许您移动分支节点(及其子节点),还允许您将它们移动到其源深度“上方”的位置。

但同样 - 您需要花时间了解代码在做什么,并准备根据您的需要对其进行修改

import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.DropMode;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.TransferHandler;
import static javax.swing.TransferHandler.COPY_OR_MOVE;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

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];

        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)) 
                System.out.println("Unsupported flavor");
                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;
                
            
            // This seems to stop the node from been copied to a level above itself?!
//            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;
        

        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>();

                for (int i = 0; i < paths.length; i++) 
                    DefaultMutableTreeNode next = (DefaultMutableTreeNode) paths[i].getLastPathComponent();
                    System.out.println("Selected = " + next.getUserObject());
                    copies.add(next);
                
                DefaultMutableTreeNode[] nodes = copies.toArray(new DefaultMutableTreeNode[copies.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) 
            // Already dealt with this
        

        public int getSourceActions(JComponent c) 
            return COPY_OR_MOVE;
        

        public boolean importData(TransferHandler.TransferSupport support) 
            System.out.println("Import here");
            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();
            

            // One consideration to consider is what to do if it's a different
            // source model ... and it's only a "copy" operation
            // Might want to look into that
            for (DefaultMutableTreeNode node : nodes) 
                model.removeNodeFromParent(node);
            

            // Add data to model.
            for (DefaultMutableTreeNode node : nodes) 
                model.insertNodeInto(node, 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);
            
        
    

就“我在网上找到的代码”而言,这还不错。

【讨论】:

非常感谢,我没想过简单地删除验证我的数据的代码!我调整了我的课程以几乎反映您的改进,并且我已经实现了我的自定义数据模型。 TransferHandler 中的所有内容现在都像魅力一样工作!我目前仍然收到由ConcurrentModificationException 引起的RunTimeException,但这似乎是由于我根据TreeNodes 更新模型的处理。移动超过 13 年的 CR-Thread,现在这应该是关于 d&d 与 Swing 组件的必链接答案!

以上是关于如何获取选定的行,包括 JTree 的子行的主要内容,如果未能解决你的问题,请参考以下文章

如何从QTableView中获取选定的行数

如何在 QTableView 中获取选定的行

如何在 Devexpress MVC GridView 上获取选定的行?

如何在 KendoUI 中获取选定的行及其数据项?

MySql JOIN 3 个表并获取具有相同值的子行

Slickgrid:如何使用新的复选框选择列插件获取选定的行?