Я работал с состоянием дерева (расширенные/выбранные узлы), сохраняя и создавая класс утилиты, который может сохранять и восстанавливать состояния узлов. Он работает нормально.Восстановление состояний расширенных/свернутых состояний дерева
Но все же существует одна проблема с самим JTree: пока пользователь работает с некоторым экземпляром JTree (расширяя/сворачивая узлы), может возникнуть ситуация, когда какой-то узел (скрытый под другим свернутым узлом) расширяется. Ничего особенного в этом нет - все в порядке.
JTree ведет записи о расширенных/свернутых узлах в отдельном expandedState
Hashtable, используя путь узла как ключевой и логический как расширенное значение состояния. Поэтому, когда этот расширенный узел под свернутым родительским узлом станет видимым, он будет по-прежнему расширяться, поскольку для него есть запись в expandedState
Hashtable с значением true
.
Ситуация объясняется на скриншотах ...
1. Раскройте корень и расширить некоторый узел ("glassfish4" папку) под корнем:
2. Collapse корень:
3. Раскройте корень снова и мы все еще видим дочерний узел («glassfish4» папка) расширенный:
Представьте себе, что я спас состояние дерева на данный момент скриншот # 2, когда корень свернут - проблема в том, что если я хочу восстановить все состояния узлов дерева (даже для скрытых), я не могу развернуть узел под другим свернутым узлом, потому что это заставит все родительские узлы расширяться. Также я не могу получить доступ к expandedState
Hashtable для изменения расширенных состояний непосредственно внутри него, так как он объявлен приватным в JTree и нет хороших способов доступа к нему. Поэтому я не могу полностью воспроизвести исходное состояние дерева.
Так что я могу сделать, это:
- Насильно доступ, что Hashtable через отражение - действительно плохая идея
- Rewrite JTree узлы расширения логики - это тоже плохая идея
- Восстановление всех расширенных состояний первой затем восстановить все сложенные состояния - это заставит дерево делать дополнительные бессмысленные рецензии и много дополнительного рендеринга, так что это очень плохое решение, которое я не хочу использовать.
Может быть, я пропустил что-то еще?
Так что в основном вопрос:
Есть ли другой способ развернуть дочерние узлы, не вызывая расширение родительских узлов?
Вы можете найти несколько классов, которые я использую для сохранения/восстановления состояния дерева ниже.
Просто позвоните TreeUtils.getTreeState(tree)
, чтобы получить состояние JTree и TreeUtils.setTreeState(tree,treeState)
, чтобы восстановить состояние JTree. Обратите внимание, что дерево должно использовать UniqueNode, иначе эти методы будут бросать ClassCastException - вы можете просто заменить DefaultMutableTreeNode на UniqueNode, если у вас есть собственные узлы, расширяющие DefaultMutableTreeNode.
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
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 пожалуйста можно поймать эти события, проследить из TreeModelListener, используя TreeExpansionListener и TreeWillExpandListener (или ошибка в вашем L & F, стандартные вопросы с JList, JComboBox, JSpinner и JTree и его Model_To_View) – mKorbel
хороший вопрос - затрудняюсь ответить : да, даже JTree - это только ведомое состояние расширения, реальный контроллер - это AbstractLayoutCache, созданный и используемый делегатом ui. Поэтому я подозреваю (никогда не пробовал), что реальное решение будет envolve пользовательского layoutCache, который требует пользовательских uis ... – kleopatra
@mKorbel его действительно возможность прослушать расширение дерева и расширить скрытые узлы, когда они станут видимыми, но это Обходной путь - лучший, но все же обходной путь. Я хочу восстановить состояние дерева сразу, не оставляя следов (например, ленивых слушателей расширения) восстановления «инструмента». –