简体   繁体   English

在将一些节点添加到底层模型后,如何刷新JTree?

[英]How can I refresh a JTree after adding some nodes to the underlying model?

First of all, let me say that I dont use the DefaultTreeModel. 首先,我要说我不使用DefaultTreeModel。 I implement my own TreeModel, so i cant use the DefaultXXX stuff. 我实现了自己的TreeModel,所以我不能使用DefaultXXX。 The problem is this: Through some addStuff() methods which my model defines I add nodes to the underlying data structure. 问题是:通过我的模型定义的一些addStuff()方法,我将节点添加到底层数据结构。 I then notify listeners by calling treeNodesChanged() inside the addStuff() function (I Know there are treeNodesInserted methods but it is the same thing. It just notifies listeners with a different method). 然后我通过在addStuff()函数中调用treeNodesChanged()来通知监听器(我知道有treeNodesInserted方法,但它是一样的。它只是用不同的方法通知监听器)。 Now, one of the listeners is a static class in my main form and this listener can tell the JTree, which is also contained in my main form, to refresh itself. 现在,其中一个监听器是我的主窗体中的静态类,这个监听器可以告诉JTree,它也包含在我的主窗体中,用于刷新自身。 How do I tell the JTree to "reload" some or all of its nodes from the model? 如何告诉JTree从模型中“重新加载”其部分或全部节点?

UPDATE: Found this question that although not exactly the same, it gives the answer I want. 更新:发现这个问题虽然不完全相同,但它给出了我想要的答案。

UPDATE 2: My problem was not how to notify the viewer (the JTree), but rather in what way should the jtree be reloaded after the notification from the model. 更新2:我的问题不是如何通知查看器(JTree),而是在模型通知后应该以什么方式重新加载jtree。

First of all let me say that the only way i know to refresh a tree to reflect underlying changes, is to call the updateUI(), or reuse the setModel() method. 首先让我说,我知道刷新树以反映底层更改的唯一方法是调用updateUI(),或重用setModel()方法。 Essentially, my problem is this: 基本上,我的问题是:

Suppose the TreeModelListener has just been notified (through the TreeModelListener API) that a change has occured in the model. 假设刚刚通知TreeModelListener(通过TreeModelListener API)模型中发生了更改。 Ok, what now? 好的,现在呢?

I have this problem because the JTree does not implement TreeModelListener. 我有这个问题,因为JTree没有实现TreeModelListener。 So the listener, in my situation, is the JTree's container, or an internal class implementing the Listener, living under the same container as Jtree. 因此,在我的情况下,监听器是JTree的容器,或实现监听器的内部类,与Jtree位于同一容器中。

So suppose I am a TreeModelListener implementation, living happily in a JForm with my brother JTree. 所以假设我是一个TreeModelListener实现,和我的兄弟JTree一起幸福地生活在一个JForm中。 Suddenly my method treeNodesInserted(TreeModelEvent evt) is called. 突然我的方法treeNodesInserted(TreeModelEvent evt)被调用。 What do I do now? 现在我该怎么做? If i call Jtree.updateUI() from inside me, then the model's listeners List throws ConcurrentModification Exception. 如果我从我内部调用Jtree.updateUI(),那么模型的侦听器List会抛出ConcurrentModification异常。 Can I call something else other than updateUI? 我可以调用updateUI以外的其他东西吗?

I tried a number of things, but only updateUI refreshed the JTree. 我尝试了很多东西,但只有updateUI刷新了JTree。 So I did it outside of the listener. 所以我在听众之外做了。 From the JForm, I just call the model's method that alters the undrlying structure, and then i call updateUI. 从JForm,我只是调用模型的方法来改变不正常的结构,然后我调用updateUI。 No TreeModelListener gets used. 没有使用TreeModelListener。

UPDATE3: I found out that there are implicit TreeModelListeners registered. UPDATE3:我发现注册了隐式TreeModelListeners。 In my model's addTreeModelListener(TreeModelListener listener) implementation i put a debug system.out line: 在我的模型的addTreeModelListener(TreeModelListener监听器)实现中,我放了一个debug system.out行:

System.out.println("listener added: " + listener.getClass().getCanonicalName());

and I saw this debug output just when I executed jTree.setModel(model): 当我执行jTree.setModel(model)时,我看到了这个调试输出:

listener added: javax.swing.JTree.TreeModelHandler 监听器添加:javax.swing.JTree.TreeModelHandler

listener added: javax.swing.plaf.basic.BasicTreeUI.Handler 监听器添加:javax.swing.plaf.basic.BasicTreeUI.Handler

The ConcurrentModificationException is caused because a call to jtree.updateUI() re registers the listener (only the plaf, not both) so it is thrown when i call updateUI inside a listener notification loop. 引发ConcurrentModificationException是因为对jtree.updateUI()的调用会重新注册侦听器(只有plaf,而不是两者),因此当我在侦听器通知循环中调用updateUI时会抛出它。 The only way now to refresh the tree is do it outside of TreeModelListener. 现在刷新树的唯一方法是在TreeModelListener之外进行。 Any comments or ideas for a better solution? 有关更好解决方案的任何意见或想法吗? Am I missing something? 我错过了什么吗?

I faced the same "problem": calling treeNodesInserted() did not cause my JTree to update its contents. 我遇到了同样的“问题”:调用treeNodesInserted()并没有导致我的JTree更新其内容。

But the problem was in other place: I used wrong constructor for TreeModelEvent . 但问题出在其他地方:我为TreeModelEvent使用了错误的构造函数。 I thought that I can create TreeModelEvent for treeNodesInserted() like that: 我以为我可以为treeNodesInserted()创建TreeModelEvent

//-- Wrong!!
TreePath path_to_inserted_item = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(my_source, path_to_inserted_item);

This doesn't work. 这不起作用。

As stated in TreeModelEvent docs , this constructor is only needed for treeStructureChanged() . TreeModelEvent文档中所述 ,只有treeStructureChanged()才需要此构造函数。 But for treeNodesInserted() , treeNodesRemoved() , treeNodesChanged() we should use another constructor: 但是对于treeNodesInserted()treeNodesRemoved()treeNodesChanged()我们应该使用另一个构造函数:

TreePath path_to_parent_of_inserted_items = /*....*/ ;
int[] indices_of_inserted_items = /*....*/ ;
Object[] inserted_items = /*....*/ ;
TreeModelEvent tme = new TreeModelEvent(
      my_source,
      path_to_parent_of_inserted_items,
      indices_of_inserted_items,
      inserted_items
   );

This code works, and JTree updates its contents properly. 此代码有效, JTree正确更新其内容。


UPD: Actually, docs are unclear about using these TreeModelEvent s, and especially with JTree , so, I want to tell about some questions that came to me when I tried to figure out how to deal with all this stuff. UPD:实际上,文档不清楚使用这些TreeModelEvent ,尤其是JTree ,所以,我想告诉我在试图找出如何处理所有这些内容时遇到的一些问题。

Firstly, as Paralife noted it his comment, cases when nodes are inserted/changed/removed, or when tree structure is changed, aren't orthogonal. 首先,当Paralife注意到他的评论时,插入/更改/删除节点或更改树结构时的情况不是正交的。 So, 所以,

Question #1: when should we use treeNodesInserted() / Changed() / Removed() , and when treeStructureChanged() ? 问题#1:我们何时应该使用treeNodesInserted() / Changed() / Removed() ,以及何时使用treeStructureChanged()

Answer: treeNodesInserted() / Changed() / Removed() can be used if only all the affected nodes have the same parent. 答案:如果只有所有受影响的节点具有相同的父节点,则可以使用treeNodesInserted() / Changed() / Removed() Otherwise you may make several calls to these methods, or just call treeStructureChanged() once (and pass the root node of affected nodes to it). 否则,您可以对这些方法进行多次调用,或者只调用一次treeStructureChanged() (并将受影响节点的根节点传递给它)。 So, treeStructureChanged() is a kind of universal way, while treeNodesInserted() / Changed() / Removed() are more specific. 因此, treeStructureChanged()是一种通用方式,而treeNodesInserted() / Changed() / Removed()则更具体。

Question #2: As far as treeStructureChanged() is a universal way, why do I need to deal with these treeNodesInserted() / Changed() / Removed() ? 问题2:treeStructureChanged()是一种通用方式而言,为什么我需要处理这些treeNodesInserted() / Changed() / Removed() Just call to treeStructureChanged() seems to be easier. 只需调用treeStructureChanged()似乎更容易。

Answer: If you use JTree to display contents of your tree, then the following thing might be a surprize for you (as it was for me) : when you call treeStructureChanged() , then JTree doesn't keep expand state of sub-nodes! 答:如果您使用JTree来显示树的内容,那么下面的内容可能会让您treeStructureChanged()就像我一样):当您调用treeStructureChanged()JTree不会保持子节点的展开状态! Consider the example, here's contents of our JTree now: 考虑一下这个例子,这里是我们JTree的内容:

[A]
 |-[B]
 |-[C]
 |  |-[E]
 |  |  |-[G]
 |  |  |-[H]
 |  |-[F]
 |     |-[I]
 |     |-[J]
 |     |-[K]
 |-[D]

Then you make some changes to C (say, rename it to C2 ), and you call treeStructureChanged() for that: 然后你对C进行一些更改(比如,将它重命名为C2 ),然后treeStructureChanged()调用treeStructureChanged()

  myTreeModel.treeStructureChanged(
        new TreeModelEvent(
           this,
           new Object[] { myNodeA, myNodeC } // Path to changed node
           )
        );

Then, nodes E and F will be collapsed! 然后,节点EF将被折叠! And your JTree will look like that: 你的JTree看起来像那样:

[A]
 |-[B]
 |-[C2]
 |  +-[E]
 |  +-[F]
 |-[D]

To avoid that, you should use treeNodesChanged() , like that: 为避免这种情况,您应该使用treeNodesChanged() ,如下所示:

  myTreeModel.treeNodesChanged(
        new TreeModelEvent(
           this,
           new Object[] { myNodeA }, // Path to the _parent_ of changed item
           new int[] { 1 },          // Indexes of changed nodes
           new Object[] { myNodeC }, // Objects represents changed nodes
                                     //    (Note: old ones!!! 
                                     //     I.e. not "C2", but "C",
                                     //     in this example)
           )
        );

Then, expanding state will be kept. 然后,将保持扩大状态。


I hope this post will be useful for somebody. 我希望这篇文章对某些人有用。

I've always found the TreeModel a bit confusing. 我总是发现TreeModel有点令人困惑。 I agree with the above statement that the model should notify the view when a change is made so the view can repaint itself. 我同意上述声明,模型应该在进行更改时通知视图,以便视图可以重新绘制自己。 However, this does not seem to be the case when using the DefaultTreeModel. 但是,使用DefaultTreeModel时似乎并非如此。 I find you need to invoke the reload() method after updating the model. 我发现你需要在更新模型后调用reload()方法。 Something like: 就像是:

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);

I also found implementing TreeModel a bit confusing when the tree consist of more than just Folders(root) and Files(child), so I've used the DefaultTreeModel. 我还发现,当树不仅包含文件夹(根)和文件(子)时,实现TreeModel有点令人困惑,所以我使用了DefaultTreeModel。 This works for me. 这适合我。 The method creates or refreshes the JTree. 该方法创建或刷新JTree。 Hope this helps. 希望这可以帮助。

    public void constructTree() {       

        DefaultMutableTreeNode root =
                new DefaultMutableTreeNode(UbaEnv.DB_CONFIG);
        DefaultMutableTreeNode child = null;

        HashMap<String, DbConfig> dbConfigs = env.getDbConfigs();
        Iterator it = dbConfigs.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pair = (Map.Entry) it.next();
            child = new DefaultMutableTreeNode(pair.getKey());

            child.add(new DefaultMutableTreeNode(UbaEnv.PROP));
            child.add(new DefaultMutableTreeNode(UbaEnv.SQL));
            root.add(child);
        }

        if (tree == null) {
            tree = new JTree(new DefaultTreeModel(root));
            tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
            tree.addTreeSelectionListener(new TreeListener());
        } else {
            tree.setModel(new DefaultTreeModel(root));
        }
}

Yesterday I struggeled around to fix the same issue. 昨天我为解决同样的问题而苦苦挣扎。 The requirement was to insert and remove nodes on the fly, without collapsing the the expanded tree nodes. 要求是动态插入和删除节点,而不折叠展开的树节点。 I browsed the web and found a bunch of possible solutions, until I stumbled over this thread. 我浏览了网页并找到了一堆可能的解决方案,直到我偶然发现了这个问题。 Then I applied the anwser from 'Dmitry Frank' with the TreeModelEvent . 然后我使用TreeModelEvent应用来自'Dmitry Frank'的anwser。 I was a bit confused, why it is such a big issue to just insert or remove a simple node and let the rest of the JTree untouched! 我有点困惑,为什么只是插入或删除一个简单的节点并让JTree的其余部分不受影响这是一个大问题! Finally the plain vanilla examples from java2s helped me to find the probably simplest solution at all. 最后,来自java2s的简单的vanilla示例帮助我找到了最简单的解决方案。 (Neither a call like: nodeStructureChanged , nodeChanged , nodesWereRemoved , nodesWereInserted , etc. nor a TreeModelEvent like suggested by 'Dmitry Frank' was required.) (既不需要调用诸如: nodeStructureChangednodeChangednodesWereRemovednodesWereInserted等,也不需要像'Dmitry Frank'建议的TreeModelEvent 。)


Here's my solution: 这是我的解决方案:

// Remove a node
treeModel.removeNodeFromParent(myNode);

// Insert a node (Actually this is a reinsert, since I used a Map called `droppedNodes` to keep the previously removed nodes. So I don't need to reload the data items of those nodes.)
MyNode root = treeModel.getRootNode();
treeModel.insertNodeInto(droppedNodes.get(nodeName), root, root.getChildCount());

Keep it simple ;) 把事情简单化 ;)

Your TreeModel is supposed to fire TreeModelEvents when it changes, and the JTree observes your model though a TreeModelListener to refresh itself when your model changes. 您的TreeModel应该在更改时触发TreeModelEvents,并且JTree通过TreeModelListener观察您的模型,以便在模型更改时自行刷新。

So if you implement the TreeModelListener support correctly , you do not need to observe the model and inform the JTree, as it already does so itself. 因此,如果正确实现TreeModelListener支持 ,则无需观察模型并通知JTree,因为它本身就已经这样做了。 From an MVC perspecive, the JTree is your View/Controller, and the TreeModel is your Model (or rather: model adapter), which is observable. 从MVC的角度来看,JTree是您的View / Controller,而TreeModel是您的Model(或者更确切地说:模型适配器),它是可观察的。

You could try force the JTree to update its visual state by calling repaint(), but I would recommend not doing so as it's not guaranteed to work. 您可以尝试通过调用repaint()来强制JTree更新其可视状态,但我建议不要这样做,因为它不能保证工作。 When you're unsure of how to do a fine-granular notification to a TreeModelListener, use TreeModelListener.treeStructureChanged(..) to notify a 'whole model' update (warning: can cause selections and node expansion states to be lost). 如果您不确定如何对TreeModelListener进行细粒度通知,请使用TreeModelListener.treeStructureChanged(..)来通知“整个模型”更新(警告:可能导致选择和节点扩展状态丢失)。

FINAL UPDATE: Found the problem and solved it : The following steps solve the problem, (but see the accepted answer for a better solution and a deep explanation of the problem) : 最终更新:找到问题并解决问题 :以下步骤解决了问题, (但请参阅已接受的答案以获得更好的解决方案并对问题进行深入解释)

  1. The implicit listeners registered are enough to do the job. 注册的隐式监听器足以完成这项工作。 No need to implement my own listener. 无需实现我自己的监听器。
  2. When adding nodes and calling treeNodesInserted() it doesn't work (JTree not updated). 添加节点并调用treeNodesInserted()它不起作用(JTree未更新)。 But It works with calling treeStructureChanged() . 但它适用于调用treeStructureChanged()

Apparently the implicit listeners are internally refreshing the tree the way i want, but only in their treeStructureChanged() method implementation. 显然,隐式侦听器以我想要的方式在内部刷新树,但仅在其treeStructureChanged()方法实现中。 It would be good for JTree to provide this "algorithm" as a function in order to be able to be called manually. JTree将这个“算法”作为一个函数提供,以便能够手动调用。

For example:Jtree Refresh Dynamically 例如:Jtree Refresh Dynamically

package package_name;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

public final class homepage extends javax.swing.JFrame implements ActionListener
{
     DefaultTreeModel model;
     DefaultMutableTreeNode root;
     File currentDir = new File("database");

public homepage() throws InterruptedException {
        initComponents();
    //------full screen window
        this.setExtendedState(MAXIMIZED_BOTH);
    //====================Jtree Added Details..   

    //result is the variable name for jtree
        model =(DefaultTreeModel) treedbnm.getModel();
    //gets the root of the current model used only once at the starting
        root=(DefaultMutableTreeNode) model.getRoot();
    //function called   
        dbcreate_panel1();
        Display_DirectoryInfo(currentDir,root);

}//End homepage() Constructor
public void Display_DirectoryInfo(File dir,DefaultMutableTreeNode tmp_root) throws InterruptedException 
{..........   
}
public void dbcreate_panel1()
{
    //------------------Jtree Refresh Dynamically-------------------//
             root.removeFromParent();
             root.removeAllChildren();
             model.reload();
             Display_DirectoryInfo(currentDir,root);
}
}//End homepage

It seems to be possible to reinitialize the whole tree by setting the model to null: eg 通过将模型设置为null,似乎可以重新初始化整个树:例如

        TreePath path = tree.getSelectionPath();
        model.doChanges(); // do something with model
        tree.setModel(null);
        tree.setModel(model);
        tree.setSelectionPath(path);
        tree.expandPath(path);

Tree update works for me 树更新对我有用

kr Achim kr Achim

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM