【发布时间】:2013-08-17 12:29:04
【问题描述】:
我正在处理树状态(扩展/选择的节点)保存并制作了一个可以保存和恢复节点状态的实用程序类。它工作正常。
但是 JTree 本身仍然存在一个问题 - 当用户使用一些 JTree 实例(展开/折叠节点)时,可能会出现一些节点(隐藏在另一个折叠节点下)被展开的情况。没什么特别的 - 很好。
JTree 将有关展开/折叠节点的记录保存在单独的expandedState Hashtable 中,使用节点路径作为键,布尔值作为展开状态值。因此,当折叠的父节点下的展开节点变得可见时,它仍然会展开,因为在 expandedState Hashtable 中有一条记录,其值为 true。
屏幕截图说明的情况...
1.展开根目录,展开根目录下的一些节点(“glassfish4”文件夹):
2.折叠根:
3. 再次展开root,我们仍然看到子节点(“glassfish4”文件夹)展开:
想象一下,当 root 折叠时,我在屏幕截图 #2 时刻保存了树状态 - 问题是如果我想恢复所有树节点状态(即使是隐藏的),我无法在另一个折叠下展开节点节点,因为这将强制所有父节点展开。此外,我无法访问 expandedState Hashtable 以直接在其中更改扩展状态,因为它在 JTree 中被声明为私有并且没有访问它的好方法。所以我不能完全重现初始树状态。
所以我能做的是:
- 通过反射强制访问该 Hashtable - 真是个坏主意
- 重写 JTree 节点扩展逻辑 - 这也是个坏主意
- 首先恢复所有展开状态,然后恢复所有折叠状态 - 这将迫使树进行额外的无意义重绘和大量额外渲染,所以这是一个非常糟糕的解决方法,我不想使用
也许我错过了其他东西?
所以基本上问题是:
有没有其他方法可以扩展子节点而不导致父节点扩展?
您可以在下面找到一些我用来保存/恢复树状态的类。
只需调用TreeUtils.getTreeState(tree) 即可检索JTree 状态,调用TreeUtils.setTreeState(tree,treeState) 即可恢复JTree 状态。请注意,树必须使用 UniqueNode,否则这些方法将抛出 ClassCastException - 如果您有自己的扩展 DefaultMutableTreeNode 的节点,您可以简单地将 DefaultMutableTreeNode 替换为 UniqueNode。
UniqueNode.java - 具有自己唯一 ID 的简单节点
public class UniqueNode extends DefaultMutableTreeNode implements Serializable
{
/**
* Prefix for node ID.
*/
private static final String ID_PREFIX = "UN";
/**
* Unique node ID.
*/
protected String id;
/**
* Costructs a simple node.
*/
public UniqueNode ()
{
super ();
setId ();
}
/**
* Costructs a node with a specified user object.
*
* @param userObject custom user object
*/
public UniqueNode ( Object userObject )
{
super ( userObject );
setId ();
}
/**
* Returns node ID and creates it if it doesn't exist.
*
* @return node ID
*/
public String getId ()
{
if ( id == null )
{
setId ();
}
return id;
}
/**
* Changes node ID.
*
* @param id new node ID
*/
public void setId ( String id )
{
this.id = id;
}
/**
* Changes node ID to new random ID.
*/
private void setId ()
{
this.id = TextUtils.generateId ( ID_PREFIX );
}
/**
* {@inheritDoc}
*/
public UniqueNode getParent ()
{
return ( UniqueNode ) super.getParent ();
}
/**
* Returns TreePath for this node.
*
* @return TreePath for this node
*/
public TreePath getTreePath ()
{
return new TreePath ( getPath () );
}
}
TreeUtils.java - 将 TreeState 从 JTree 保存/加载到 JTree 的实用程序类
public class TreeUtils
{
/**
* Returns tree expansion and selection states.
* Tree nodes must be instances of UniqueNode class.
*
* @param tree tree to process
* @return tree expansion and selection states
*/
public static TreeState getTreeState ( JTree tree )
{
return getTreeState ( tree, true );
}
/**
* Returns tree expansion and selection states.
* Tree nodes must be instances of UniqueNode class.
*
* @param tree tree to process
* @param saveSelection whether to save selection states or not
* @return tree expansion and selection states
*/
public static TreeState getTreeState ( JTree tree, boolean saveSelection )
{
TreeState treeState = new TreeState ();
List<UniqueNode> elements = new ArrayList<UniqueNode> ();
elements.add ( ( UniqueNode ) tree.getModel ().getRoot () );
while ( elements.size () > 0 )
{
UniqueNode element = elements.get ( 0 );
TreePath path = new TreePath ( element.getPath () );
treeState.addState ( element.getId (), tree.isExpanded ( path ), saveSelection && tree.isPathSelected ( path ) );
for ( int i = 0; i < element.getChildCount (); i++ )
{
elements.add ( ( UniqueNode ) element.getChildAt ( i ) );
}
elements.remove ( element );
}
return treeState;
}
/**
* Restores tree expansion and selection states.
* Tree nodes must be instances of UniqueNode class.
*
* @param tree tree to process
* @param treeState tree expansion and selection states
*/
public static void setTreeState ( JTree tree, TreeState treeState )
{
setTreeState ( tree, treeState, true );
}
/**
* Restores tree expansion and selection states.
* Tree nodes must be instances of UniqueNode class.
*
* @param tree tree to process
* @param treeState tree expansion and selection states
* @param restoreSelection whether to restore selection states or not
*/
public static void setTreeState ( JTree tree, TreeState treeState, boolean restoreSelection )
{
if ( treeState == null )
{
return;
}
tree.clearSelection ();
List<UniqueNode> elements = new ArrayList<UniqueNode> ();
elements.add ( ( UniqueNode ) tree.getModel ().getRoot () );
while ( elements.size () > 0 )
{
UniqueNode element = elements.get ( 0 );
TreePath path = new TreePath ( element.getPath () );
// Restoring expansion states
if ( treeState.isExpanded ( element.getId () ) )
{
tree.expandPath ( path );
// We are going futher only into expanded nodes, otherwise this will expand even collapsed ones
for ( int i = 0; i < element.getChildCount (); i++ )
{
elements.add ( ( UniqueNode ) tree.getModel ().getChild ( element, i ) );
}
}
else
{
tree.collapsePath ( path );
}
// Restoring selection states
if ( restoreSelection )
{
if ( treeState.isSelected ( element.getId () ) )
{
tree.addSelectionPath ( path );
}
else
{
tree.removeSelectionPath ( path );
}
}
elements.remove ( element );
}
}
}
TreeState.java - 保存节点状态的地图的容器类
public class TreeState implements Serializable
{
/**
* Tree node states.
*/
protected Map<String, NodeState> states = new LinkedHashMap<String, NodeState> ();
/**
* Constructs new object instance with empty states.
*/
public TreeState ()
{
super ();
}
/**
* Constructs new object instance with specified states.
*
* @param states node states
*/
public TreeState ( Map<String, NodeState> states )
{
super ();
if ( states != null )
{
setStates ( states );
}
}
/**
* Returns all node states.
*
* @return all node states
*/
public Map<String, NodeState> getStates ()
{
return states;
}
/**
* Sets all node states.
*
* @param states all node states
*/
public void setStates ( Map<String, NodeState> states )
{
this.states = states;
}
/**
* Adds node state.
*
* @param nodeId node ID
* @param expanded expansion state
* @param selected selection state
*/
public void addState ( String nodeId, boolean expanded, boolean selected )
{
states.put ( nodeId, new NodeState ( expanded, selected ) );
}
/**
* Returns whether node with the specified ID is expanded or not.
*
* @param nodeId node ID
* @return true if node with the specified ID is expanded, false otherwise
*/
public boolean isExpanded ( String nodeId )
{
final NodeState state = states.get ( nodeId );
return state != null && state.isExpanded ();
}
/**
* Returns whether node with the specified ID is selected or not.
*
* @param nodeId node ID
* @return true if node with the specified ID is expanded, false otherwise
*/
public boolean isSelected ( String nodeId )
{
final NodeState state = states.get ( nodeId );
return state != null && state.isSelected ();
}
}
NodeState.java - 单节点扩展/选择状态
public class NodeState implements Serializable
{
/**
* Whether node is expanded or not.
*/
protected boolean expanded;
/**
* Whether node is selected or not.
*/
protected boolean selected;
/**
* Constructs empty node state.
*/
public NodeState ()
{
super ();
this.expanded = false;
this.selected = false;
}
/**
* Constructs node state with the specified expansion and selection states.
*
* @param expanded expansion state
* @param selected selection state
*/
public NodeState ( boolean expanded, boolean selected )
{
super ();
this.expanded = expanded;
this.selected = selected;
}
/**
* Returns whether node is expanded or not.
*
* @return true if node is expanded, false otherwise
*/
public boolean isExpanded ()
{
return expanded;
}
/**
* Sets whether node is expanded or not.
*
* @param expanded whether node is expanded or not
*/
public void setExpanded ( boolean expanded )
{
this.expanded = expanded;
}
/**
* Returns whether node is selected or not.
*
* @return true if node is selected, false otherwise
*/
public boolean isSelected ()
{
return selected;
}
/**
* Sets whether node is selected or not.
*
* @param selected whether node is selected or not
*/
public void setSelected ( boolean selected )
{
this.selected = selected;
}
}
顺便说一句,setTreeState 方法避免了此刻在折叠节点下恢复展开状态:
// Restoring expansion states
if ( treeState.isExpanded ( element.getId () ) )
{
tree.expandPath ( path );
// We are going futher only into expanded nodes, otherwise this will expand even collapsed ones
for ( int i = 0; i < element.getChildCount (); i++ )
{
elements.add ( ( UniqueNode ) tree.getModel ().getChild ( element, i ) );
}
}
else
{
tree.collapsePath ( path );
}
仅当父节点展开时才调用收集子节点的方法。所以折叠节点下的所有子节点都会被忽略。如果您更改该行为,您将看到我在此问题开头描述的问题 - 父节点将被扩展。
【问题讨论】:
-
+1 请使用 TreeExpansionListener 和 TreeWillExpandListener 捕获这些事件,从 TreeModelListener 跟踪(或 L&F 中的错误,JList、JComboBox、JSpinner 和 JTree 及其 Model_To_View 的标准问题)跨度>
-
好问题 - 很难回答:公平地说,即使是 JTree 也只是扩展状态的奴隶,真正的控制器是 ui 委托创建和使用的 AbstractLayoutCache。所以我怀疑(从未尝试过)一个真正的解决方案将包含一个需要自定义 uis 的自定义 layoutCache ...
-
@mKorbel 它确实是一个选项来监听树扩展并在隐藏节点变得可见时展开它们,但这是一种解决方法 - 一个更好的解决方法,但仍然是一种解决方法。我想要的是立即恢复树状态,而不会留下任何恢复“工具”存在的痕迹(如惰性扩展侦听器)。
-
@kleopatra 是的,我刚刚注意到 BasicTreeUI 类中的 AbstractLayoutCache - 我想知道它是否保留了 JTree 保存的状态副本或......
-
是的,它保留了一个包含所有已扩展节点的并行树模型
标签: java swing jtree expansion