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

时间:2009-12-16 10:50:40

标签: java swing jtree

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

更新: 发现这个question尽管不完全相同,但它给出了我想要的答案。

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

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

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

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

假设我是一个TreeModelListener实现,和我的兄弟JTree一起幸福地生活在一个JForm中。突然我的方法treeNodesInserted(TreeModelEvent evt)被调用。现在我该怎么做?如果我从我内部调用Jtree.updateUI(),那么模型的侦听器List会抛出ConcurrentModification异常。我可以调用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之外进行。有关更好解决方案的任何意见或想法吗?我错过了什么吗?

8 个答案:

答案 0 :(得分:18)

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

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

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

这不起作用。

TreeModelEvent docs所述,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() / {{1 }}?只需致电Removed()似乎更容易。

答案:如果您使用treeStructureChanged()来显示树的内容,那么以下内容可能会让您感到惊讶(就像我一样):当您致电{{ 1}},然后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 ) ); 将被折叠!而你的E将会是这样的:

F

为避免这种情况,您应该使用JTree,例如:

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

然后,将保持扩大状态。


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

答案 1 :(得分:14)

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

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

答案 2 :(得分: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));
        }
}

答案 3 :(得分:4)

昨天我一直在努力解决同样的问题。 要求是动态插入和删除节点,而不会折叠展开的树节点。 我浏览了网页并找到了一堆可能的解决方案,直到我偶然发现这个帖子。然后我用'TreeModelEvent将'Dmitry Frank'的anwser应用了。我有点困惑,为什么只是插入或删除一个简单的节点并让JTree的其余部分保持不变是一个很大的问题! 最后,来自java2s的普通香草例子帮助我找到了最简单的解决方案。 (不需要像“nodeStructureChangednodeChangednodesWereRemovednodesWereInserted等,也不需要像”德米特里·弗兰克“所建议的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());

保持简单;)

答案 4 :(得分:3)

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

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

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

答案 5 :(得分:2)

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

  1. 注册的隐式侦听器足以完成这项工作。无需实现我自己的监听器。
  2. 添加节点并调用treeNodesInserted()时,它不起作用(JTree未更新)。但它适用于调用treeStructureChanged()
  3. 显然,隐式侦听器按照我想要的方式在内部刷新树,但仅在其treeStructureChanged()方法实现中。 JTree将这个“算法”作为一个函数提供,以便能够手动调用。

答案 6 :(得分: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

答案 7 :(得分:0)

似乎可以通过将模型设置为null来重新初始化整个树: e.g。

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

树更新对我有用

kr Achim