[英]How can I refresh a JTree after adding some nodes to the underlying model?
首先,我要说我不使用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会抛出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之外进行。 有关更好解决方案的任何意见或想法吗? 我错过了什么吗?
我遇到了同样的“问题”:调用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
)
);
然后,节点E
和F
将被折叠! 你的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)
)
);
然后,将保持扩大状态。
我希望这篇文章对某些人有用。
我总是发现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<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));
}
}
昨天我为解决同样的问题而苦苦挣扎。 要求是动态插入和删除节点,而不折叠展开的树节点。 我浏览了网页并找到了一堆可能的解决方案,直到我偶然发现了这个问题。 然后我使用TreeModelEvent
应用来自'Dmitry Frank'的anwser。 我有点困惑,为什么只是插入或删除一个简单的节点并让JTree
的其余部分不受影响这是一个大问题! 最后,来自java2s的简单的vanilla示例帮助我找到了最简单的解决方案。 (既不需要调用诸如: nodeStructureChanged
, nodeChanged
, nodesWereRemoved
, nodesWereInserted
等,也不需要像'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());
把事情简单化 ;)
您的TreeModel应该在更改时触发TreeModelEvents,并且JTree通过TreeModelListener观察您的模型,以便在模型更改时自行刷新。
因此,如果正确实现TreeModelListener支持 ,则无需观察模型并通知JTree,因为它本身就已经这样做了。 从MVC的角度来看,JTree是您的View / Controller,而TreeModel是您的Model(或者更确切地说:模型适配器),它是可观察的。
您可以尝试通过调用repaint()来强制JTree更新其可视状态,但我建议不要这样做,因为它不能保证工作。 如果您不确定如何对TreeModelListener进行细粒度通知,请使用TreeModelListener.treeStructureChanged(..)来通知“整个模型”更新(警告:可能导致选择和节点扩展状态丢失)。
最终更新:找到问题并解决问题
:以下步骤解决了问题, (但请参阅已接受的答案以获得更好的解决方案并对问题进行深入解释) :
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);
树更新对我有用
kr Achim
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.