在向基础模型添加一些节点后,如何刷新 JTree?

2022-09-02 23:02:15

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

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

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

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

假设树模型管理器刚刚收到通知(通过树模型管理器 API),模型中发生了更改。好吧,现在该怎么办?

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

所以假设我是一个TreeModelListener实现,和我的兄弟JTree一起快乐地生活在JForm中。突然,我的方法树NodesInserted(TreeModelEvent evt)被调用。我现在该怎么办?如果我从内部调用Jtree.updateUI(),那么模型的侦听器List会抛出并发修改异常。我可以调用 updateUI 以外的其他内容吗?

我尝试了许多方法,但只有 updateUI 刷新了 JTree。所以我在听众之外做了这件事。从JForm中,我只调用模型的方法,该方法更改了undrlying结构,然后我调用了updateUI。没有使用树模型列表。

更新3:我发现有隐式的TreeModelListeners注册。在我模型的addTreeModelListener(TreeModelListener listener)实现中,我放了一个调试system.out行:

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

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

听众添加: javax.swing.JTree.TreeModelHandler

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

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


答案 1

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

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

//-- 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:实际上,文档不清楚如何使用这些 s,尤其是 ,所以,我想谈谈当我试图弄清楚如何处理所有这些东西时出现的一些问题。TreeModelEventJTree

首先,正如Paralife在他的评论中指出的那样,插入/更改/删除节点或更改树结构的情况不是正交的。VoI

问题#1:我们什么时候应该使用//,什么时候应该使用?treeNodesInserted()Changed()Removed()treeStructureChanged()

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

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

答:如果您用于显示树的内容,那么以下事情对您来说可能是一个惊喜(就像我一样):当您调用时,则不会保持子节点的扩展状态!考虑一下这个例子,以下是我们现在的内容:JTreetreeStructureChanged()JTreeJTree

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

然后,对 (例如,将其重命名为 ) 进行一些更改,并调用:CC2treeStructureChanged()

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

然后,节点和将被折叠!你的遗嘱看起来像这样:EFJTree

[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!!! 
                                     //     I.e. not "C2", but "C",
                                     //     in this example)
           )
        );

然后,将保留扩展状态。


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


答案 2

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

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

推荐