17

首先,让我说我不使用 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(),则模型的侦听器列表会引发 ConcurrentModification 异常。我可以调用 updateUI 以外的其他名称吗?

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

UPDATE3:我发现有隐式 TreeModelListeners 注册。在我的模型的 addTreeModelListener(TreeModelListener listener) 实现中,我放了一个调试 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 之外进行。对于更好的解决方案有任何意见或想法吗?我错过了什么吗?

4

8 回答 8

21

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

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

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

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

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

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

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

答:如果您使用JTree显示树的内容,那么以下事情可能会让您感到惊讶(就像对我一样):当您调用时treeStructureChanged()JTree不会保持子节点的展开状态!考虑这个例子,这里是我们JTree现在的内容:

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

C然后您对(例如,将其重命名为C2)进行一些更改,并要求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!!! 
                                     //     I.e. not "C2", but "C",
                                     //     in this example)
           )
        );

然后,将保持扩展状态。


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

于 2013-01-18T21:16:43.407 回答
17

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

DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
root.add(new DefaultMutableTreeNode("another_child"));
model.reload(root);
于 2009-12-16T16:57:50.900 回答
6

昨天我努力解决同样的问题。要求是动态插入和删除节点,而不会折叠展开的树节点。我浏览了网络并找到了一堆可能的解决方案,直到我偶然发现了这个线程。然后我将“Dmitry Frank”中的 anwser 与TreeModelEvent. 我有点困惑,为什么只插入或删除一个简单的节点而让其余部分JTree保持不变是一个如此大的问题!最后,来自java2s的普通示例帮助我找到了可能最简单的解决方案。(不需要像:nodeStructureChanged, nodeChanged, nodesWereRemoved,nodesWereInserted等这样的电话,也不TreeModelEvent需要'Dmitry Frank'建议的类似电话。)


这是我的解决方案:

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

把事情简单化 ;)

于 2014-02-15T11:11:17.913 回答
5

当树不仅仅包含文件夹(根)和文件(子)时,我还发现实现 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));
        }
}
于 2010-02-03T03:09:54.633 回答
3

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

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

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

于 2009-12-16T11:06:13.357 回答
2

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

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

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

于 2009-12-17T10:34:03.360 回答
1

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

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

树更新对我有用

克瑞阿奇姆

于 2017-07-05T20:54:19.103 回答
0

例如: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
于 2016-08-20T05:51:05.533 回答