Java 向底层模型添加一些节点后,如何刷新 JTree?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1913855/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 23:49:52  来源:igfitidea点击:

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

javaswingjtree

提问by Paralife

First of all, let me say that I dont use the DefaultTreeModel. I implement my own TreeModel, so i cant use the DefaultXXX stuff. The problem is this: Through some addStuff() methods which my model defines I add nodes to the underlying data structure. 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). 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. How do I tell the JTree to "reload" some or all of its nodes from the model?

首先,让我说我不使用 DefaultTreeModel。我实现了我自己的 TreeModel,所以我不能使用 DefaultXXX 的东西。问题是这样的:通过我的模型定义的一些 addStuff() 方法,我将节点添加到底层数据结构中。然后我通过在 addStuff() 函数中调用 treeNodesChanged() 来通知侦听器(我知道有 treeNodesInserted 方法,但它是同一回事。它只是用不同的方法通知侦听器)。现在,其中一个侦听器是我的主窗体中的静态类,并且该侦听器可以告诉 JTree(也包含在我的主窗体中)刷新自身。我如何告诉 JTree 从模型“重新加载”它的部分或全部节点?

UPDATE: Found this questionthat 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. Essentially, my problem is this:

首先让我说,我知道刷新树以反映底层更改的唯一方法是调用 updateUI() 或重用 setModel() 方法。本质上,我的问题是这样的:

Suppose the TreeModelListener has just been notified (through the TreeModelListener API) that a change has occured in the model. Ok, what now?

假设 TreeModelListener 刚刚被通知(通过 TreeModelListener API)模型中发生了变化。好的,现在怎么办?

I have this problem because the JTree does not implement 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 没有实现 TreeModelListener。因此,在我的情况下,侦听器是 JTree 的容器,或实现侦听器的内部类,与 Jtree 位于同一容器下。

So suppose I am a TreeModelListener implementation, living happily in a JForm with my brother JTree. Suddenly my method treeNodesInserted(TreeModelEvent evt) is called. What do I do now? If i call Jtree.updateUI() from inside me, then the model's listeners List throws ConcurrentModification Exception. Can I call something else other than updateUI?

所以假设我是一个 TreeModelListener 实现,和我的兄弟 JTree 幸福地生活在一个 JForm 中。突然我的方法 treeNodesInserted(TreeModelEvent evt) 被调用。现在我该怎么做?如果我从内部调用 Jtree.updateUI(),那么模型的侦听器列表会抛出 ConcurrentModification 异常。我可以调用 updateUI 以外的其他东西吗?

I tried a number of things, but only updateUI refreshed the 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. No TreeModelListener gets used.

我尝试了很多东西,但只有 updateUI 刷新了 JTree。所以我在听众之外做了。在 JForm 中,我只调用模型的方法来改变底层结构,然后调用 updateUI。没有使用 TreeModelListener。

UPDATE3: I found out that there are implicit TreeModelListeners registered. In my model's addTreeModelListener(TreeModelListener listener) implementation i put a debug system.out line:

UPDATE3:我发现有隐式 TreeModelListeners 注册。在我的模型的 addTreeModelListener(TreeModelListener listener) 实现中,我放置了一个调试 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

listener added: javax.swing.plaf.basic.BasicTreeUI.Handler

添加了侦听器:javax.swing.JTree.TreeModelHandler

添加了侦听器: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. The only way now to refresh the tree is do it outside of TreeModelListener. Any comments or ideas for a better solution? Am I missing something?

ConcurrentModificationException 是因为对 jtree.updateUI() 的调用重新注册了侦听器(只有 plaf,而不是两者),所以当我在侦听器通知循环中调用 updateUI 时会抛出它。现在刷新树的唯一方法是在 TreeModelListener 之外进行。对更好的解决方案有什么意见或想法吗?我错过了什么吗?

采纳答案by Dmitry Frank

I faced the same "problem": calling treeNodesInserted()did not cause my JTreeto update its contents.

我遇到了同样的“问题”:调用treeNodesInserted()并没有导致我JTree更新其内容。

But the problem was in other place: I used wrong constructor for TreeModelEvent. I thought that I can create TreeModelEventfor treeNodesInserted()like that:

但问题出在其他地方:我为TreeModelEvent. 我以为我可以创建TreeModelEventtreeNodesInserted()这样的:

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

This doesn't work.

这不起作用。

As stated in TreeModelEventdocs, this constructor is only needed for treeStructureChanged(). But for treeNodesInserted(), treeNodesRemoved(), treeNodesChanged()we should use another constructor:

TreeModelEvent文档中所述,此构造函数仅用于treeStructureChanged(). 但是对于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 JTreeupdates its contents properly.

此代码有效,并JTree正确更新其内容。



UPD:Actually, docs are unclear about using these TreeModelEvents, 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:实际上,文档不清楚如何使用这些TreeModelEvents,尤其是使用JTree,所以,我想谈谈当我试图弄清楚如何处理所有这些东西时遇到的一些问题。

Firstly, as Paralife noted it his comment, cases when nodes are inserted/changed/removed, or when tree structure is changed, aren't orthogonal. So,

首先,正如 Paralife 指出的他的评论,插入/更改/删除节点或更改树结构时的情况不是正交的。所以,

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. Otherwise you may make several calls to these methods, or just call treeStructureChanged()once (and pass the root node of affected nodes to it). So, treeStructureChanged()is a kind of universal way, while treeNodesInserted()/Changed()/Removed()are more specific.

答:treeNodesInserted()/ Changed()/Removed()可如果只是所有受影响的节点具有相同的父使用。否则你可以多次调用这些方法,或者只调用treeStructureChanged()一次(并将受影响节点的根节点传递给它)。因此,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()? Just call to treeStructureChanged()seems to be easier.

问题2:至于treeStructureChanged()是一个普遍的方式,为什么我需要处理这些treeNodesInserted()/ Changed()/ Removed()?只是打电话treeStructureChanged()似乎更容易。

Answer:If you use JTreeto 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 JTreedoesn't keep expand state of sub-nodes! Consider the example, here's contents of our JTreenow:

答:如果你习惯JTree显示你的树的内容,那么下面的事情对你来说可能是一个惊喜(对我来说也是如此):当你调用时treeStructureChanged(),然后JTree不保持子节点的展开状态!考虑这个例子,这是我们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()

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

Then, nodes Eand Fwill be collapsed! And your JTreewill look like that:

然后,节点EF将被折叠!你的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.

我希望这篇文章对某人有用。

回答by Peter Walser

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. From an MVC perspecive, the JTree is your View/Controller, and the TreeModel is your Model (or rather: model adapter), which is observable.

因此,如果您正确实现了 TreeModelListener 支持,则不需要观察模型并通知 JTree,因为它本身已经这样做了。从 MVC 的角度来看,JTree 是您的视图/控制器,而 TreeModel 是您的模型(或者更确切地说:模型适配器),这是可观察的。

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. 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).

您可以尝试通过调用 repaint() 强制 JTree 更新其视觉状态,但我建议不要这样做,因为它不能保证工作。当您不确定如何对 TreeModelListener 进行细粒度通知时,请使用 TreeModelListener.treeStructureChanged(..) 来通知“整个模型”更新(警告:可能导致选择和节点扩展状态丢失)。

回答by camickr

I've always found the TreeModel a bit confusing. 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. I find you need to invoke the reload() method after updating the model. Something like:

我一直觉得 TreeModel 有点令人困惑。我同意上面的说法,即模型应该在进行更改时通知视图,以便视图可以重新绘制自己。但是,在使用 DefaultTreeModel 时,情况似乎并非如此。我发现您需要在更新模型后调用 reload() 方法。就像是:

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

回答by Paralife

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). But It works with calling treeStructureChanged().
  1. 注册的隐式侦听器足以完成这项工作。无需实现我自己的监听器。
  2. 添加节点并调用treeNodesInserted()它时不起作用(JTree 未更新)。但它适用于调用treeStructureChanged().

Apparently the implicit listeners are internally refreshing the tree the way i want, but only in their treeStructureChanged()method implementation. It would be good for JTree to provide this "algorithm" as a function in order to be able to be called manually.

显然,隐式侦听器正在按照我想要的方式在内部刷新树,但仅限于它们的treeStructureChanged()方法实现。JTree 最好将此“算法”作为函数提供,以便能够手动调用。

回答by Ariel

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. This works for me. The method creates or refreshes the JTree. Hope this helps.

我还发现当树不仅仅包含文件夹(根)和文件(子)时,实现 TreeModel 有点混乱,所以我使用了 DefaultTreeModel。这对我有用。该方法创建或刷新 JTree。希望这可以帮助。

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

回答by My-Name-Is

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. 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 JTreeuntouched! Finally the plain vanilla examples from java2shelped me to find the probably simplest solution at all. (Neither a call like: nodeStructureChanged, nodeChanged, nodesWereRemoved, nodesWereInserted, etc. nor a TreeModelEventlike suggested by 'Dmitry Frank' was required.)

昨天我努力解决同样的问题。要求是即时插入和删除节点,而不会折叠展开的树节点。我浏览了网络并找到了一堆可能的解决方案,直到我偶然发现了这个线程。然后我将 'Dmitry Frank' 中的 anwser 应用到TreeModelEvent. 我有点困惑,为什么只插入或删除一个简单的节点而让其余节点JTree保持原样是一个如此大的问题!最后,来自java2s的普通示例帮助我找到了可能最简单的解决方案。(既不像一个电话:nodeStructureChangednodeChangednodesWereRemovednodesWereInserted,等,也不是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 ;)

把事情简单化 ;)

回答by Chetan Bhagat

For example:Jtree Refresh Dynamically

例如:Jtree 动态刷新

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

回答by herberlin

It seems to be possible to reinitialize the whole tree by setting the model to null: e.g.

似乎可以通过将模型设置为 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

克尔阿希姆