JTree setselectionpath花费了太长的时间来扩展。

时间:2021-06-08 12:38:12

I am using JTree and I am selecting all the nodes in the tree (Ctrl-A). My tree contains 14000 parents and each parent has a child, so in total the tree contains 28000 nodes. Following is the code snippet:

我正在使用JTree并选择树中的所有节点(Ctrl-A)。我的树包含14000个父节点,并且每个父节点都有一个子节点,所以这棵树总共包含28000个节点。下面是代码片段:

@Override public final void setSelectionPaths(TreePath[] aPaths)
  {
    if (aPaths != null)
   {

   for (TreePath path : aPaths)
   {
       TreePath parentPath = path.getParentPath();
       if (parentPath != null)
        {
          expandPath(path.getParentPath());
        }
      }
    }
    super.setSelectionPaths(aPaths);
  }

It takes 20 minutes to expand the tree. Is there a way to optimize it?

把树展开需要20分钟。有没有办法优化它?

2 个解决方案

#1


-1  

I found that after recording the selected paths, if you reset the selection before expanding, it's incredibly faster.

我发现在记录了所选路径之后,如果你在扩展之前重置选择,它会非常快。

final TreePath[] paths = tree.getSelectionPaths();
//Modifying the selection makes it run a whole lot faster
tree.setSelectionPath(paths[0]);
for(int i = 0; i < paths.length; i++){
    tree.expandPath(paths[i]);
}

I assume this is because it doesn't have to do so much magic with the selection any more.

我认为这是因为它不再需要对选择做太多的改动。

Through the fear of being beaten up and left for dead, I'll provide this suggestion. If you want to leave the GUI unlocked during this process, you could throw it onto a new Thread (not on the EDT). Of course you have to be really careful that nothing tries to interact at all with the Tree - Swing isn't thread safe, so anything other than looking at it will cause all sorts of funky problems.

由于害怕挨打被遗弃而死亡,我将提出这个建议。如果您想让GUI在此过程中解锁,您可以将其扔到新的线程上(而不是在EDT上)。当然,您必须非常小心,不要试图与树进行任何交互——Swing不是线程安全的,因此,除了查看它之外的任何内容都将导致各种奇怪的问题。

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.tree.*;

public class JTreeExpanding extends Box{

    //Just to make sure no user interactions happen during expansion
    JPanel glassPane = new JPanel(){
        public void paintComponent(Graphics g){
            g.setColor(new Color(0,0,0,80));
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(Color.white);
            g.setFont(g.getFont().deriveFont(18f).deriveFont(Font.BOLD));
            g.drawString("Processing...", getWidth()-100, getHeight()-10);
        }
    };

    public JTreeExpanding(){
        super(BoxLayout.Y_AXIS);

        glassPane.setOpaque(false);

        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        for(int i = 0; i < 14000; i++){
            DefaultMutableTreeNode node = new DefaultMutableTreeNode("Root" + i);
            node.add(new DefaultMutableTreeNode("Child" + i));
            root.add(node);
        }   

        final JTree tree = new JTree(root);
        tree.setRootVisible(false);
        final JScrollPane pane = new JScrollPane(tree);
        add(pane);

        JButton button = new JButton("Expand");
        button.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                //Taking the expand off the EDT frees up GUI 
                Thread t = new Thread(new Runnable(){
                    @Override
                    public void run() {
                        final TreePath[] paths = tree.getSelectionPaths();
                        //Modifying the selection makes it run a whole lot faster
                        tree.setSelectionPath(paths[0]);
                        for(int i = 0; i < paths.length; i++){
                            tree.expandPath(paths[i]);
                        }
                        glassPane.setVisible(false);
                    }});

                getRootPane().setGlassPane(glassPane);
                glassPane.setVisible(true);
                t.start();
            }});
        add(button);

        //Allow Scrolling in scroll pane while Tree is expanding
        glassPane.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                for(MouseWheelListener mwl : pane.getMouseWheelListeners()){
                    mwl.mouseWheelMoved(e);
                }
            }
        });

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new JTreeExpanding());
        frame.validate();
        frame.pack();
        frame.setVisible(true);
    }

}

#2


-1  

To satisfy myself and @kleopatra, I've come up with a better implementation (which on my machine saves ~8 seconds over the previous answer). Basically it adds another method to JTree for expanding selected paths.

为了满足我自己和@kleopatra,我想出了一个更好的实现(在我的机器上,它比之前的答案节省了大约8秒)。基本上,它向JTree添加了另一个方法来扩展所选路径。

This method cuts out some of the overhead of not necessary with expanding selected nodes, and fires UI updates after all the internal states have been set. The method is based on the native implementation of JTable.setExpandedState. I've also left code in so you can see the performance differences on your machine.

该方法省去了扩展所选节点时不必要的开销,并在设置所有内部状态后触发UI更新。我还留了一些代码,以便您可以看到您的机器上的性能差异。

Best of all - it's not messing with the EDT anymore (because at 3.5 seconds for 14k nodes... why would you).

最重要的是,它不再干扰EDT(因为14k节点在3.5秒内……)为什么你)。

import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.tree.*;

public class JTreeExpanding extends Box{

    public JTreeExpanding(){
        super(BoxLayout.Y_AXIS);

        //Populating a sample tree
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        for(int i = 0; i < 14000; i++){
            DefaultMutableTreeNode node = new DefaultMutableTreeNode("Root" + i);
            node.add(new DefaultMutableTreeNode("Child" + i));
            root.add(node);
        }   

        //Create a custom tree
        final CustomTree tree = new CustomTree(root);
        //final JTree tree = new JTree(root);
        tree.setRootVisible(false);
        final JScrollPane pane = new JScrollPane(tree);
        add(pane);

        //Create a button to expand the selected nodes
        JButton button = new JButton("Expand");
        button.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                long start = System.currentTimeMillis();
                //New way using Custom JTree
                tree.expandSelectedPaths();

                //Old way using classic JTree
                /*TreePath[] paths = tree.getSelectionPaths();
                tree.setSelectionPath(paths[0]);
                for(TreePath path : paths)
                    tree.expandPath(path);*/
                System.out.println(System.currentTimeMillis() - start);
            }});
        add(button);

    }

    public static class CustomTree extends JTree{

        HashMap<TreePath, Boolean> expandedState = new HashMap<TreePath, Boolean>();
        Stack<TreePath> customExpandedStack = new Stack<TreePath>();

        public CustomTree(DefaultMutableTreeNode root) {
            super(root);
        }

        public void expandSelectedPaths(){

            final TreePath[] paths = getSelectionPaths();
            setSelectionPath(paths[0]);

            for(TreePath path: paths){
                TreePath parentPath = path.getParentPath();

                while(parentPath != null) {
                    if(isExpanded(parentPath)) {
                        parentPath = null;
                    }
                    else {
                        customExpandedStack.push(parentPath);
                        parentPath = parentPath.getParentPath();
                    }
                }

                for(int counter = customExpandedStack.size() - 1; counter >= 0; counter--) {
                    parentPath = customExpandedStack.pop();
                    if(!isExpanded(parentPath)) {
                        expandedState.put(parentPath, Boolean.TRUE);
                    }
                }
            }

            if (accessibleContext != null) {
                ((AccessibleJTree)accessibleContext).
                fireVisibleDataPropertyChange();
            }

            for(TreePath path : paths){
                fireTreeExpanded(path);
                try {
                    fireTreeWillExpand(path);
                } catch (ExpandVetoException eve) {
                    // Expand vetoed!
                    return;
                }
            }
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new JTreeExpanding());
        frame.validate();
        frame.pack();
        frame.setVisible(true);
    }

}

#1


-1  

I found that after recording the selected paths, if you reset the selection before expanding, it's incredibly faster.

我发现在记录了所选路径之后,如果你在扩展之前重置选择,它会非常快。

final TreePath[] paths = tree.getSelectionPaths();
//Modifying the selection makes it run a whole lot faster
tree.setSelectionPath(paths[0]);
for(int i = 0; i < paths.length; i++){
    tree.expandPath(paths[i]);
}

I assume this is because it doesn't have to do so much magic with the selection any more.

我认为这是因为它不再需要对选择做太多的改动。

Through the fear of being beaten up and left for dead, I'll provide this suggestion. If you want to leave the GUI unlocked during this process, you could throw it onto a new Thread (not on the EDT). Of course you have to be really careful that nothing tries to interact at all with the Tree - Swing isn't thread safe, so anything other than looking at it will cause all sorts of funky problems.

由于害怕挨打被遗弃而死亡,我将提出这个建议。如果您想让GUI在此过程中解锁,您可以将其扔到新的线程上(而不是在EDT上)。当然,您必须非常小心,不要试图与树进行任何交互——Swing不是线程安全的,因此,除了查看它之外的任何内容都将导致各种奇怪的问题。

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.tree.*;

public class JTreeExpanding extends Box{

    //Just to make sure no user interactions happen during expansion
    JPanel glassPane = new JPanel(){
        public void paintComponent(Graphics g){
            g.setColor(new Color(0,0,0,80));
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(Color.white);
            g.setFont(g.getFont().deriveFont(18f).deriveFont(Font.BOLD));
            g.drawString("Processing...", getWidth()-100, getHeight()-10);
        }
    };

    public JTreeExpanding(){
        super(BoxLayout.Y_AXIS);

        glassPane.setOpaque(false);

        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        for(int i = 0; i < 14000; i++){
            DefaultMutableTreeNode node = new DefaultMutableTreeNode("Root" + i);
            node.add(new DefaultMutableTreeNode("Child" + i));
            root.add(node);
        }   

        final JTree tree = new JTree(root);
        tree.setRootVisible(false);
        final JScrollPane pane = new JScrollPane(tree);
        add(pane);

        JButton button = new JButton("Expand");
        button.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                //Taking the expand off the EDT frees up GUI 
                Thread t = new Thread(new Runnable(){
                    @Override
                    public void run() {
                        final TreePath[] paths = tree.getSelectionPaths();
                        //Modifying the selection makes it run a whole lot faster
                        tree.setSelectionPath(paths[0]);
                        for(int i = 0; i < paths.length; i++){
                            tree.expandPath(paths[i]);
                        }
                        glassPane.setVisible(false);
                    }});

                getRootPane().setGlassPane(glassPane);
                glassPane.setVisible(true);
                t.start();
            }});
        add(button);

        //Allow Scrolling in scroll pane while Tree is expanding
        glassPane.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                for(MouseWheelListener mwl : pane.getMouseWheelListeners()){
                    mwl.mouseWheelMoved(e);
                }
            }
        });

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new JTreeExpanding());
        frame.validate();
        frame.pack();
        frame.setVisible(true);
    }

}

#2


-1  

To satisfy myself and @kleopatra, I've come up with a better implementation (which on my machine saves ~8 seconds over the previous answer). Basically it adds another method to JTree for expanding selected paths.

为了满足我自己和@kleopatra,我想出了一个更好的实现(在我的机器上,它比之前的答案节省了大约8秒)。基本上,它向JTree添加了另一个方法来扩展所选路径。

This method cuts out some of the overhead of not necessary with expanding selected nodes, and fires UI updates after all the internal states have been set. The method is based on the native implementation of JTable.setExpandedState. I've also left code in so you can see the performance differences on your machine.

该方法省去了扩展所选节点时不必要的开销,并在设置所有内部状态后触发UI更新。我还留了一些代码,以便您可以看到您的机器上的性能差异。

Best of all - it's not messing with the EDT anymore (because at 3.5 seconds for 14k nodes... why would you).

最重要的是,它不再干扰EDT(因为14k节点在3.5秒内……)为什么你)。

import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import javax.swing.tree.*;

public class JTreeExpanding extends Box{

    public JTreeExpanding(){
        super(BoxLayout.Y_AXIS);

        //Populating a sample tree
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        for(int i = 0; i < 14000; i++){
            DefaultMutableTreeNode node = new DefaultMutableTreeNode("Root" + i);
            node.add(new DefaultMutableTreeNode("Child" + i));
            root.add(node);
        }   

        //Create a custom tree
        final CustomTree tree = new CustomTree(root);
        //final JTree tree = new JTree(root);
        tree.setRootVisible(false);
        final JScrollPane pane = new JScrollPane(tree);
        add(pane);

        //Create a button to expand the selected nodes
        JButton button = new JButton("Expand");
        button.addActionListener(new ActionListener(){
            @Override
            public void actionPerformed(ActionEvent e) {
                long start = System.currentTimeMillis();
                //New way using Custom JTree
                tree.expandSelectedPaths();

                //Old way using classic JTree
                /*TreePath[] paths = tree.getSelectionPaths();
                tree.setSelectionPath(paths[0]);
                for(TreePath path : paths)
                    tree.expandPath(path);*/
                System.out.println(System.currentTimeMillis() - start);
            }});
        add(button);

    }

    public static class CustomTree extends JTree{

        HashMap<TreePath, Boolean> expandedState = new HashMap<TreePath, Boolean>();
        Stack<TreePath> customExpandedStack = new Stack<TreePath>();

        public CustomTree(DefaultMutableTreeNode root) {
            super(root);
        }

        public void expandSelectedPaths(){

            final TreePath[] paths = getSelectionPaths();
            setSelectionPath(paths[0]);

            for(TreePath path: paths){
                TreePath parentPath = path.getParentPath();

                while(parentPath != null) {
                    if(isExpanded(parentPath)) {
                        parentPath = null;
                    }
                    else {
                        customExpandedStack.push(parentPath);
                        parentPath = parentPath.getParentPath();
                    }
                }

                for(int counter = customExpandedStack.size() - 1; counter >= 0; counter--) {
                    parentPath = customExpandedStack.pop();
                    if(!isExpanded(parentPath)) {
                        expandedState.put(parentPath, Boolean.TRUE);
                    }
                }
            }

            if (accessibleContext != null) {
                ((AccessibleJTree)accessibleContext).
                fireVisibleDataPropertyChange();
            }

            for(TreePath path : paths){
                fireTreeExpanded(path);
                try {
                    fireTreeWillExpand(path);
                } catch (ExpandVetoException eve) {
                    // Expand vetoed!
                    return;
                }
            }
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new JTreeExpanding());
        frame.validate();
        frame.pack();
        frame.setVisible(true);
    }

}