【问题标题】:Dynamic JTree and SwingUtilities.invokeLater() does nothing动态 JTree 和 SwingUtilities.invokeLater() 什么都不做
【发布时间】:2012-02-13 11:36:06
【问题描述】:

为了创建我的动态 JTree,我在this website in chapter "4.2 OutlineNode.java"阅读有关动态 JTree 的教程

现在我已经实现了它,并且认识到在 GUI 线程中加载数据需要很长时间并且也很丑陋。因此我添加了一个线程来扩展子元素,然后将 TreeNode 元素添加到树中。

private void getChildNodes() {
    areChildrenDefined = true;
    Thread t = new Thread(new Runnable()
    {
        @Override
        public void run() {
        System.out.println("Expand");
            final List<DECTTreeNode> listNodes = new ArrayList<DECTTreeNode>();
            if (castNode().canExpand())
            {
                for(DECTNode crt : castNode().getChildren())
                {   
                    DECTTreeNode treeNode = new DECTTreeNode(crt);
                    listNodes.add(treeNode);
                }

                try {
                    SwingUtilities.invokeAndWait(new Runnable()
                    {
                        @Override
                        public void run() {
                            System.out.println(listNodes.size());
                            for (DECTTreeNode crt : listNodes)
                            {
                                add(crt); // <==== Adds the node to the JTree
                            }
                        }

                    });
                    //}).run();
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

    });
    t.start();
}

没有线程它可以正常工作。如果我添加线程并将add-calls 放入SwingUtilities.invokeAndWait(...),子项似乎已展开,但它们在树中不可见。

我已经在树上尝试了revalidate()repaint() - 没有任何效果。

知道如何让这些元素可见吗?

提前谢谢你。

【问题讨论】:

  • 我认为这段代码没有做任何事情,如果包含某些内容,请您打印输出 TreeModel 中的节点
  • 如前所述:它似乎在没有线程的情况下工作。如果我展开它们并在同一个线程中显示所有节点,它就可以工作。

标签: java swing jtree invokelater


【解决方案1】:

检查您的 add() 方法是否触发了正确的 TreeModelEvent

【讨论】:

  • 对造成的混乱感到抱歉。我的问题中的 sn-p 是 extends DefaultMutableTreeNode 类的一部分。因此,add() 执行父级操作,DefaultMutableTreeNode 执行操作。
  • 那就是问题所在。调用 add() 时 DefaultMutableTreeNode 不会触发事件。您需要通过模型添加节点,请参阅 DefaultTreeModel#insertNodeInto (或手动触发正确的事件)。
【解决方案2】:

要扩展 Walter +1 的建议,请从 JButton addButton 调用 Runnable#Thread

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

public class DynamicTreeDemo extends JPanel implements ActionListener {

    private static final long serialVersionUID = 1L;
    private int newNodeSuffix = 1;
    private static String ADD_COMMAND = "add";
    private static String REMOVE_COMMAND = "remove";
    private static String CLEAR_COMMAND = "clear";
    private DynamicTree treePanel;

    public DynamicTreeDemo() {
        super(new BorderLayout()); // Create the components.
        treePanel = new DynamicTree();
        populateTree(treePanel);
        JButton addButton = new JButton("Add");
        addButton.setActionCommand(ADD_COMMAND);
        addButton.addActionListener(this);
        JButton removeButton = new JButton("Remove");
        removeButton.setActionCommand(REMOVE_COMMAND);
        removeButton.addActionListener(this);
        JButton clearButton = new JButton("Clear");
        clearButton.setActionCommand(CLEAR_COMMAND);
        clearButton.addActionListener(this);  // Lay everything out.
        treePanel.setPreferredSize(new Dimension(300, 150));
        add(treePanel, BorderLayout.CENTER);
        JPanel panel = new JPanel(new GridLayout(0, 3));
        panel.add(addButton);
        panel.add(removeButton);
        panel.add(clearButton);
        add(panel, BorderLayout.SOUTH);
    }

    public void populateTree(DynamicTree treePanel) {
        String p1Name = "Parent 1";
        String p2Name = "Parent 2";
        String c1Name = "Child 1";
        String c2Name = "Child 2";
        DefaultMutableTreeNode p1, p2;
        p1 = treePanel.addObject(null, p1Name);
        p2 = treePanel.addObject(null, p2Name);
        treePanel.addObject(p1, c1Name);
        treePanel.addObject(p1, c2Name);
        treePanel.addObject(p2, c1Name);
        treePanel.addObject(p2, c2Name);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (ADD_COMMAND.equals(command)) { // Add button clicked
            treePanel.addObject("New Node " + newNodeSuffix++);
        } else if (REMOVE_COMMAND.equals(command)) { // Remove button clicked
            treePanel.removeCurrentNode();
        } else if (CLEAR_COMMAND.equals(command)) { // Clear button clicked.
            treePanel.clear();
        }
    }


    private static void createAndShowGUI() { // Create and set up the window.
        JFrame frame = new JFrame("DynamicTreeDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Create and set up the content pane.
        DynamicTreeDemo newContentPane = new DynamicTreeDemo();
        newContentPane.setOpaque(true); // content panes must be opaque
        frame.setContentPane(newContentPane);  // Display the window.
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}


class DynamicTree extends JPanel {
    private static final long serialVersionUID = 1L;

    private DefaultMutableTreeNode rootNode;
    private DefaultTreeModel treeModel;
    private JTree tree;
    private Toolkit toolkit = Toolkit.getDefaultToolkit();

    public DynamicTree() {
        super(new GridLayout(1, 0));
        rootNode = new DefaultMutableTreeNode("Root Node");
        treeModel = new DefaultTreeModel(rootNode);
        treeModel.addTreeModelListener(new MyTreeModelListener());
        tree = new JTree(treeModel);
        tree.setEditable(true);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setShowsRootHandles(true);
        JScrollPane scrollPane = new JScrollPane(tree);
        add(scrollPane);
    }

    public void clear() {
        rootNode.removeAllChildren();
        treeModel.reload();
    }

    public void removeCurrentNode() {
        TreePath currentSelection = tree.getSelectionPath();
        if (currentSelection != null) {
            DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) (currentSelection.getLastPathComponent());
            MutableTreeNode parent = (MutableTreeNode) (currentNode.getParent());
            if (parent != null) {
                treeModel.removeNodeFromParent(currentNode);
                return;
            }
        } // Either there was no selection, or the root was selected.
        toolkit.beep();
    }


    public DefaultMutableTreeNode addObject(Object child) {
        DefaultMutableTreeNode parentNode = null;
        TreePath parentPath = tree.getSelectionPath();
        if (parentPath == null) {
            parentNode = rootNode;
        } else {
            parentNode = (DefaultMutableTreeNode) (parentPath.getLastPathComponent());
        }
        return addObject(parentNode, child, true);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child) {
        return addObject(parent, child, false);
    }

    public DefaultMutableTreeNode addObject(DefaultMutableTreeNode parent, Object child, boolean shouldBeVisible) {
        DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(child);
        if (parent == null) {
            parent = rootNode;
        }
        // It is key to invoke this on the TreeModel, and NOT DefaultMutableTreeNode
        treeModel.insertNodeInto(childNode, parent, parent.getChildCount());
        // Make sure the user can see the lovely new node.
        if (shouldBeVisible) {
            tree.scrollPathToVisible(new TreePath(childNode.getPath()));
        }
        return childNode;
    }



    class MyTreeModelListener implements TreeModelListener {

        @Override
        public void treeNodesChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("The user has finished editing the node.");
            System.out.println("New value NodesChanged: " + node.getUserObject());
        }

        @Override
        public void treeNodesInserted(TreeModelEvent e) {
            DefaultMutableTreeNode node= (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesInserted : " + node.getUserObject());
        }

        @Override
        public void treeNodesRemoved(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value NodesRemoved : " + node.getUserObject());
        }

        @Override
        public void treeStructureChanged(TreeModelEvent e) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) (e.getTreePath().getLastPathComponent());            /*
             * If the event lists children, then the changed node is the child of the
             * node we've already gotten. Otherwise, the changed node and the
             * specified node are the same.
             */ int index = e.getChildIndices()[0];
            node = (DefaultMutableTreeNode) (node.getChildAt(index));
            System.out.println("New value StructureChanged : " + node.getUserObject());
        }
    }
}

【讨论】:

    【解决方案3】:
    @Override
    public void treeWillExpand(TreeExpansionEvent e) throws ExpandVetoException {
        CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
        t.addChildLoadedListener(new ChildLoadedListener() {
            @Override
            public void childLoaded(TreeNode parent) {
                ((CustomTreeNode) parent).setExpanded(true);
                expandPath(new TreePath(((CustomTreeNode) parent).getPath()));
            }
        });
        if (!t.isExpanded()) {
            factory.loadChildren(t);
            throw new ExpandVetoException(null);
        }
    }
    
    public void treeWillCollapse(TreeExpansionEvent e) throws ExpandVetoException {
        CustomTreeNode t = (CustomTreeNode) e.getPath().getLastPathComponent();
        t.setExpanded(false);
    }
    

    判断我与否。这对我有用。 CustomTreeNode 是从 defaultMutableTreeNode 扩展而来,并添加了一个自写的 ChildLoadedListener,它在子节点被工厂加载时调用。 isExpanded 布尔值是为了避免无限循环。 工厂创建了一个 SwingWorker 来加载孩子并执行它。之后调用 ChilLoadedListener 并再次展开树。

    希望这会帮助或至少帮助你思考你的问题;-)

    编辑:

    @Override
    public void loadChildren(CustomTreeNode tn) {
        ctn = tn;
        LoadChildrenWorker worker = new LoadChildrenWorker();
        worker.execute();
    }
    
    
    private class LoadChildrenWorker extends SwingWorker<String, Object> {
    
                @Override
        protected String doInBackground() throws Exception {
                        //load source here and return a string when finished.
                        //In my case its a string repesentation of a directory
        }
    
        @Override
        protected void done() {
                //with get(), you get the string from doBackground()
                for (String str : parseFromOutput(get())) {
                        if (str.endsWith("/")) {
                            ctn.add(new CustomTreeNode("Directory");
                        } else {
                            ctn.add(new CustomTreeNode("Leaf");
                        }
                }
                //call listeners
                ctn.fireChildrenLoaded();
        }
    

    【讨论】:

    • 感谢您的回复。你能告诉我你的SwingWorkerfinished() 方法吗?在遍历先前注册的侦听器时,我以某种方式得到了ConcurrentModificationException。非常感谢
    【解决方案4】:

    我现在正在从事的项目中遇到了同样的问题。

    我使用TreeWillExpandListener 来确定何时必须加载我的树。 (延迟加载) 当树展开时,我捕获了节点并将其加载到线程中,因为我必须从服务器输出中解析节点。

    您面临的问题是树在加载您的孩子之前扩展。所以你必须抛出一个ExpandVetoException 或类似的东西,然后等到你的孩子装满。然后扩展你的树。在这种情况下,一切都会正确显示。

    希望能解决你的问题。

    扩展-> 停止扩展-> loadchildren-> addChildren -> 现在扩展树-> 查看你的节点

    编辑:

    如果你使用 Swing,你最好使用 SwingWorker。对我来说效果更好。

    【讨论】:

    • 即使我没有这样尝试,我估计它也行不通:折叠节点时,我不删除内容。因此,在再次折叠和重新展开节点时它应该是可见的。但事实并非如此。还是这是另一个不同的问题?
    • 我遇到了同样的问题。每次树打开时,它们都会加载孩子。每次加载孩子之前,它都会打开树。 System.out.println()是我们的朋友 ;-)(正如我以前的老师经常说的)
    • 无意冒犯,但即使您的解决方案似乎对您有用,我认为这看起来像一个丑陋的黑客?嗯......如前所述:我有一个标志,可确保子项仅扩展一次。它们将被保存在一个列表中并返回......
    【解决方案5】:

    尝试将invokeAndWait 替换为invokeLater

    【讨论】:

      猜你喜欢
      • 2011-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-27
      • 1970-01-01
      • 1970-01-01
      • 2021-07-31
      • 2019-02-21
      相关资源
      最近更新 更多