Восстановление развернутых / свернутых состояний узлов дерева
Я работал с сохранением состояния дерева (расширенные / выбранные узлы) и создал служебный класс, который может сохранять и восстанавливать состояния узлов. Работает нормально.
Но все же есть одна проблема с самим JTree - когда пользователь работает с некоторым экземпляром JTree (разворачивающиеся / сворачивающиеся узлы), может возникнуть ситуация, когда какой-то узел (скрытый под другим свернутым узлом) раскрывается. Ничего особенного в этом нет - это просто прекрасно.
JTree хранит записи о развернутых / свернутых узлах в отдельном expandedState
Хеш-таблица с использованием пути к узлу в качестве ключа и логического значения в качестве расширенного значения состояния. Поэтому, когда этот развернутый узел под свернутым родительским узлом станет видимым, он все равно будет раскрыт, поскольку для него есть запись в expandedState
Hashtable с true
значение.
Ситуация объяснена на скриншотах...
1. Разверните root и разверните какой-нибудь узел (папка "glassfish4") под root:
2. Свернуть корень:
3. Снова разверните root, и мы все еще увидим расширенный дочерний узел (папка "glassfish4"):
Представьте, что я сохранил состояние дерева в момент скриншота #2, когда рут свернут - проблема в том, что если я хочу восстановить все состояния узлов дерева (даже для скрытых), я не могу развернуть узел под другим свернутым узлом, потому что это заставит все родительские узлы расширяться. Также я не могу получить доступ expandedState
Hashtable для изменения расширенных состояний непосредственно внутри него, так как он объявлен как закрытый в JTree, и нет хороших способов получить к нему доступ. Поэтому я не могу полностью воспроизвести начальное состояние дерева.
Так что я могу сделать это:
- Принудительно получить доступ к этому Hashtable через отражение - действительно плохая идея
- Переписать узлы JTree расширяют логику - это тоже плохая идея
- Сначала восстановите все развернутые состояния, а затем восстановите все свернутые состояния - это заставит дерево делать дополнительные бессмысленные перерисовки и много дополнительного рендеринга, так что это действительно плохой обходной путь, который я не хочу использовать
Может я что-то упускаю?
Так что в основном вопрос заключается в следующем:
Есть ли другой способ расширить дочерние узлы, не вызывая расширение родительских узлов?
Ниже вы можете найти несколько классов, которые я использую для сохранения / восстановления состояния дерева.
Просто позвони TreeUtils.getTreeState(tree)
чтобы получить состояние JTree и TreeUtils.setTreeState(tree,treeState)
восстановить состояние JTree. Обратите внимание, что дерево должно использовать UniqueNode, в противном случае эти методы будут вызывать ClassCastException - вы можете просто заменить DefaultMutableTreeNode на UniqueNode, если у вас есть собственные узлы, расширяющие DefaultMutableTreeNode.
UniqueNode.java - простой узел с собственным уникальным идентификатором
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
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 ответ
Почему бы не восстановить состояние, выполнив те же действия, как описано, сначала установить расширенные подузлы, а затем при необходимости свернуть их родительский узел?
Единственное отличие от вашего текущего кода - использовать две итерации вместо одной. Сначала выполните итерацию и разверните, где необходимо, затем выполните итерацию и сверните, где необходимо.
В любом случае дерево должно рисовать один раз из-за логики перекраски.