恢复展开/折叠的树节点状态

时间:2013-08-15 10:14:41

标签: java swing jtree expansion

我正在使用树状态(扩展/选定节点)保存并创建了一个可以保存和恢复节点状态的实用程序类。它工作正常。

但是JTree本身仍存在一个问题 - 当用户正在使用一些JTree实例(扩展/折叠节点)时,可能会出现某些节点(隐藏在另一个折叠节点下)被扩展的情况。没什么特别的 - 这很好。

JTree将扩展/折叠节点的记录保存在单独的expandedState Hashtable中,使用节点路径作为键,布尔值作为扩展状态值。因此,当折叠的父节点下的展开节点将变为可见时,它仍将展开,因为expandedState Hashtable中有true值的记录。

情况说明截图...
1.展开root并展开root下的某个节点(“glassfish4”文件夹):
enter image description here
2.折叠root:
enter image description here
3.再次展开root,我们仍然看到子节点(“glassfish4”文件夹)已展开:
enter image description here

想象一下,我在截屏#2时刻保存了树状态,当root崩溃时 - 问题是如果我想恢复所有树节点状态(即使是隐藏的状态)我也无法扩展另一个崩溃下的节点节点,因为这将强制所有父节点扩展。此外,我无法访问expandedState Hashtable直接在其中更改扩展状态,因为它在JTree中被声明为私有,并且没有好的方法来访问它。所以我无法完全重现初始树状态。

所以我能做的是:

  1. 通过反思强制访问Hashtable - 非常糟糕的主意
  2. 重写JTree节点扩展逻辑 - 这也是一个坏主意
  3. 首先恢复所有展开状态,然后恢复所有折叠状态 - 这将迫使树进行额外的无意义重绘和大量额外渲染,因此这是一个非常糟糕的解决方法我不想使用
  4. 也许我错过了其他的东西?

    基本上问题是:
    是否有其他方法可以扩展子节点而不会导致父节点扩展?

    您可以在下面找到我用来保存/恢复树状态的几个类。

    只需调用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 - 从/向JTree保存/加载TreeState的实用程序类

    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 个答案:

答案 0 :(得分:1)

为什么不通过执行与描述相同的操作来恢复状态,首先将子节点设置为展开,然后根据需要将其父节点设置为折叠?

与当前代码的唯一区别是使用两次迭代而不是一次。首先在需要的地方进行迭代和扩展,然后在需要的地方进行迭代和折叠。

由于重绘逻辑,树应该只绘制一次。