JTree更新节点而不折叠

12

我有一个Java SE 7应用程序,需要更新 JTree 节点。根据 Oracle 提供的教程,使用此 线程,没有给出如何在代码中更新标签(树节点上显示的文本)的提示。目前,我正在使用 DefaultTreeModel 作为我的 JTree 的模型,DefaultMutableTreeNode 作为该树的节点。

为了进一步详细说明我正在开发的应用程序,我正在开发一个聊天工具,其中每个账户的联系人都显示其可用状态(在线、离线等)。

问题是,如何更新特定节点的显示文本,而无需将其从其父级中删除并添加到其指定索引中。是否可以像这样使用 DefaultMutableTreeNode.setText("<new label>") 来更新节点标签?


更新:2013年1月20日

为了澄清,重新定义了问题。


如果所有操作都在同一个线程上执行(这应该是情况),则所有操作将是顺序的。最糟糕的情况是,如果更新太频繁,它可能会变慢,但更新将一个接一个地运行。 - assylias
当我调用DefaultTreeModel.reload()来更新我的JTree模型时,节点会折叠。我该如何更新特定节点而不使其他具有子节点的节点折叠? - David B
抱歉回复晚了。之前评论的讨论并没有解决这个问题。我已经更新了我的问题以便进一步澄清。 - David B
仍然不理解为什么更新节点可能是一个问题——更新的驱动因素是什么/为什么/如何? - kleopatra
当我调用treeStructureChanged()而不是treeNodesChanged()时,我遇到了令人讨厌的节点意外折叠。请参见此答案以获取详细信息,特别是其中的最后一部分,包括更改节点的示例。但是,我没有使用DefaultXXX的东西。无论如何,请展示你的代码。 - Dmitry Frank
显示剩余3条评论
4个回答

10
也许使用 'nodeChanged()' 而不是 'reload()' 可以获得你想要的效果。
DefaultTreeModel 类上有一堆方法,可以导致树的各个部分被改变和重绘。还有其他 DefaultTreeModel 上的方法只会导致重新绘制发生。
你提到了 'reload(node)' 并评论说它在调用时会导致树折叠。'reload' 会导致从该节点开始完全重绘整个子树。(但如果该节点不可见,则什么也不会改变。)这被称为“结构更改”。
'insertNodeInto()' 和 'removeNodeFromParent()' 通过添加或删除节点并绘制来修改树的结构。
我认为你需要使用的是 'nodeChanged()',因为它只是通知模型节点中有东西发生了变化,将导致其显示方式不同。也许可显示的文本现在与之前不同。也许您已更改节点中的用户对象。这就是在节点上调用 'nodeChanged()' 的时候。
你应该在自己的代码中尝试使用 'nodeChanged()' 来代替 'reload()' 调用,而在 vels4j 提供的示例程序中也是如此。这可能解决问题。
请注意,DefaultTreeModel 上还有另外两组方法,用于其他情况:
这些方法与树节点一起工作,并使用树路径来定义更改发生的位置。它们不会更改树下面的数据结构,但会通知模型发生了某些更改,以便它可以通知听众实际重新绘制事物或以其他方式响应更改。
nodesWereInserted() nodesWereRemovde() nodesChanged() nodeStructureChanged()
还有一组 fire...() 方法,用于 DefaultTreeModel 和您可能创建的任何子类内部使用。它们只是通知任何监听器发生了某些更改。请注意它们是受保护的。

+1 DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) 是正确的方法。 - chromanoid
你能提供一个像 @vels4j 提供的 SSCCE 一样的东西,以便我接受你的答案吗? - David B
4
您可以使用 @vels4j 的代码,将 reload() 调用更改为 nodeChanged()。我认为不需要进一步的操作。我希望有一种方式来分配荣誉,因为他/她完成了大部分工作,为您提供了答案。我只是添加了一个改进,以防止其崩溃并认为有结构变化。 - Lee Meador

8

希望这个简单易懂的可执行程序能够帮助您解决问题。

public class JTreeDemo  extends JPanel
    implements Runnable {

private JTree tree;
private DefaultTreeModel treeModel ;
private Random rnd = new Random();
private List<User> userList;
public JTreeDemo() {
    super( );

    //Create the nodes.
    DefaultMutableTreeNode top =
        new DefaultMutableTreeNode("Users");
    treeModel = new DefaultTreeModel(top);
    createNodes(top);

    //Create a tree that allows one selection at a time.
    tree = new JTree(treeModel);
    tree.getSelectionModel().setSelectionMode
            (TreeSelectionModel.SINGLE_TREE_SELECTION);

    //Create the scroll pane and add the tree to it. 
    JScrollPane treeView = new JScrollPane(tree);


    //Add the split pane to this panel.
    add(treeView);
}

public String getRandomStatus() {
    int nextInt = rnd.nextInt(100);
    if( nextInt%2==0) {
        return "Online";
    } else {
        return "Offline";
    }
}
@Override
public void run() {
     while(true) {
        try {   
          Thread.sleep(1000);
          int nextInt = rnd.nextInt(10);
          User user = userList.get(nextInt);
          user.setStatus(getRandomStatus());
          treeModel.nodeChanged(user);
        } catch (InterruptedException ex) {
            // handle it if necessary
        }
     }
}

private class User extends DefaultMutableTreeNode {
    public String userName;
    public String status;

    public User(String name) {
        userName = name;

    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getStatus() {
        return status;
    }

    @Override
    public String toString() {
        String color = status.equals("Online") ? "Green" : "Red";
        return "<html><b color='"+color+"'>"+
                userName +"-"+status +"</b></html>";
    }

}


private void createNodes(DefaultMutableTreeNode top) {
    userList = new ArrayList() ;
    for(int i=0;i<10;i++) {
        User u1 = new User("User " + (i+1));
        u1.setStatus("Online");
         top.add(u1);
         userList.add(u1);
    }
}

private static void createAndShowGUI() {

    JFrame frame = new JFrame("TreeDemo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    //Add content to the window.
    JTreeDemo jTreeDemo = new JTreeDemo();
    frame.add(jTreeDemo);
    frame.pack();
    frame.setVisible(true);
    // update status randomly
    Thread thread = new Thread(jTreeDemo);
    thread.start();
}

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

我已经添加了一个线程来随机更新状态,希望您可以根据需要进行修改。
输出: enter image description here 编辑: 1. 根据建议,我已经移除了reload(node)并添加了树模型的重新加载。

1
-1 a) 更新EDT模型 b) 重新加载而不是触发更改 c)(小问题)节点内的视觉装饰 - 不要为了视图原因覆盖toString,这是渲染器的任务。 - kleopatra
@kleopatra 感谢您的建议。我根据自己的知识写了答案,这就是为什么我提到“我的答案可以帮助您解决问题”的原因。请问EDT是什么? - vels4j

0

如果节点包含在树中是唯一的对象并且已经实现了equalshashCode方法(例如您展示字符串或具有唯一ID的对象来自数据库),那么这很容易。首先,您需要遍历所有展开的节点并将节点中的对象保存在一个集合中。然后执行模型更新。更新后,您需要遍历所有节点,如果它们在集合中,则展开树中的节点。
如果节点不是唯一的,则需要将完整的树路径保存在集合中(例如作为列表),并在更新后检查以展开节点。
如果对象既没有equals也没有hashCode(这两种方法都必须实现),则无法使用此变体。


1
是的,但由于我们需要遍历节点来进行更新,因此可能需要一些 CPU 时间。 - David B

0

仅供记录(我投票给Lee Meador),DefaultTreeModel#nodeChanged(javax.swing.tree.TreeNode) 是正确的方法:

public class TestFrame extends JFrame {

    public TestFrame() {
        //create gui with simple jtree (and DefaultTreeModel)
        JButton changeBtn = new JButton();
        final JTree jTree = new JTree();
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        changeBtn.setText("update selected node");
        getContentPane().add(changeBtn, java.awt.BorderLayout.PAGE_END);
        DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("root");
        DefaultMutableTreeNode treeNode2 = new DefaultMutableTreeNode("blue");
        treeNode1.add(treeNode2);
        treeNode2 = new DefaultMutableTreeNode("violet");
        DefaultMutableTreeNode treeNode3 = new DefaultMutableTreeNode("red");
        treeNode2.add(treeNode3);
        treeNode3 = new DefaultMutableTreeNode("yellow");
        treeNode2.add(treeNode3);
        treeNode1.add(treeNode2);
        jTree.setModel(new DefaultTreeModel(treeNode1));
        getContentPane().add(jTree, BorderLayout.CENTER);
        pack();
        //add listener to button, to change selected node on button click
        changeBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                DefaultMutableTreeNode dmt = (DefaultMutableTreeNode)jTree.getSelectionPath().getLastPathComponent();
                //update content/representation of selected node
                dmt.setUserObject("My update: " + new Date());
                //nodeChanged
                ((DefaultTreeModel) jTree.getModel()).nodeChanged(dmt);
            }
        });
    }

    public static void main(String args[]) {

        EventQueue.invokeLater(new Runnable() {
            public void run() {
                new TestFrame().setVisible(true);
            }
        });
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接