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

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

更新:发现这个问题虽然不完全相同,但它给出了我想要的答案。

更新2:我的问题不是如何通知查看器(JTree),而是在模型通知后应该以什么方式重新加载jtree。

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

假设刚刚通知TreeModelListener(通过TreeModelListener API)模型中发生了更改。 好的,现在呢?

我有这个问题,因为JTree没有实现TreeModelListener。 因此,在我的情况下,监听器是JTree的容器,或实现监听器的内部类,与Jtree位于同一容器中。

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

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

UPDATE3:我发现注册了隐式TreeModelListeners。 在我的模型的addTreeModelListener(TreeModelListener监听器)实现中,我放了一个debug system.out行:

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

当我执行jTree.setModel(model)时,我看到了这个调试输出:

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

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

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

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

但问题出在其他地方:我为TreeModelEvent使用了错误的构造函数。 我以为我可以为treeNodesInserted()创建TreeModelEvent

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

这不起作用。

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

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


UPD:实际上,文档不清楚使用这些TreeModelEvent ,尤其是JTree ,所以,我想告诉我在试图弄清楚如何处理所有这些东西时遇到的一些问题。

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

问题#1:我们何时应该使用treeNodesInserted() / Changed() / Removed() ,以及何时使用treeStructureChanged()

答案:如果只有所有受影响的节点具有相同的父节点,则可以使用treeNodesInserted() / Changed() / Removed() 。 否则,您可以对这些方法进行多次调用,或者只调用一次treeStructureChanged() (并将受影响节点的根节点传递给它)。 因此, treeStructureChanged()是一种通用方式,而treeNodesInserted() / Changed() / Removed()则更具体。

问题2:treeStructureChanged()是一种通用方式而言,为什么我需要处理这些treeNodesInserted() / Changed() / Removed() ? 只需调用treeStructureChanged()似乎更容易。

答:如果您使用JTree来显示树的内容,那么下面的内容可能会让您treeStructureChanged()就像我一样):当您调用treeStructureChanged()JTree不会保持子节点的展开状态! 考虑一下这个例子,这里是我们JTree的内容:

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

然后你对C进行一些更改(比如,将其重命名为C2 ),然后treeStructureChanged()调用treeStructureChanged()

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

然后,节点EF将被折叠! 你的JTree看起来像那样:

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

为避免这种情况,您应该使用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!!! // Ie not "C2", but "C", // in this example) ) ); 

然后,将保持扩大状态。


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

我总是发现TreeModel有点令人困惑。 我同意上述声明,即模型应在进行更改时通知视图,以便视图可以重新绘制自己。 但是,使用DefaultTreeModel时似乎并非如此。 我发现你需要在更新模型后调用reload()方法。 就像是:

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

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

  public void constructTree() { DefaultMutableTreeNode root = new DefaultMutableTreeNode(UbaEnv.DB_CONFIG); DefaultMutableTreeNode child = null; HashMap 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)); } } 

您的TreeModel应该在更改时触发TreeModelEvents,并且JTree通过TreeModelListener观察您的模型,以便在模型更改时自行刷新。

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

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

昨天我为了解决同样的问题而苦苦挣扎。 要求是动态插入和删除节点,而不会折叠展开的树节点。 我浏览了网页,发现了一堆可能的解决方案,直到我偶然发现了这个问题。 然后我使用TreeModelEvent应用来自’Dmitry Frank’的anwser。 我有点困惑,为什么只是插入或删除一个简单的节点并让JTree的其余部分不受影响这是一个大问题! 最后,来自java2s的简单的vanilla示例帮助我找到了最简单的解决方案。 (既不需要调用诸如: nodeStructureChangednodeChangednodesWereRemovednodesWereInserted等,也不需要像’Dmitry Frank’建议的TreeModelEvent 。)


这是我的解决方案:

 // 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()); 

把事情简单化 ;)

最终更新:找到问题并解决它 :以下步骤解决了问题, (但请参阅接受的答案以获得更好的解决方案并对问题进行深入解释)

  1. 注册的隐式侦听器足以完成这项工作。 无需实现我自己的监听器。
  2. 添加节点并调用treeNodesInserted()它不起作用(JTree未更新)。 但它适用于调用treeStructureChanged()

显然,隐式侦听器是按照我想要的方式在内部刷新树,但仅在其treeStructureChanged()方法实现中。 JTree将这个“算法”作为一个函数提供,以便能够手动调用,这将是一件好事。

例如: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 

通过将模型设置为null,似乎可以重新初始化整个树:例如

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

树更新对我有用

克阿欣