ツリーの状態 (展開/選択されたノード) の保存に取り組んでおり、ノードの状態を保存および復元できるユーティリティ クラスを作成しました。それは正常に動作します。
ただし、JTree 自体にはまだ 1 つの問題があります。ユーザーが JTree インスタンス (ノードの展開/折りたたみ) で作業しているときに、(折りたたまれた別のノードの下に隠れている) 一部のノードが展開される場合があります。それについて特別なことは何もありません-それは問題ありません。
JTree は、expandedState
ノード パスをキーとして使用し、ブール値を展開状態値として使用して、展開/折りたたまれたノードに関する記録を別の Hashtable に保持します。そのため、折りたたまれた親ノードの下にある展開されたノードが表示される場合、expandedState
Hashtable にtrue
値を持つレコードがあるため、展開されたままになります。
スクリーンショットで説明されている状況...
1. ルートを展開し、ルートの下のいくつかのノード (「glassfish4
」
フォルダー) を展開します。
ルートが折りたたまれたスクリーンショット #2 の瞬間にツリーの状態を保存したと想像してください。問題は、すべてのツリー ノードの状態を復元したい場合 (非表示のノードであっても)、別の折りたたまれたノードの下にあるノードを展開できないことです。すべての親ノードを強制的に展開します。またexpandedState
、Hashtable は JTree でプライベートに宣言されており、アクセスする良い方法がないため、Hashtable に直接アクセスして展開された状態を変更することはできません。したがって、初期のツリー状態を完全に再現することはできません。
だから私ができることは次のとおりです。
- リフレクションを介してその Hashtable に強制的にアクセスする - 本当に悪い考え
- JTree ノードの展開ロジックを書き直す - これも悪い考えです
- 最初にすべての展開された状態を復元してから、すべての折りたたまれた状態を復元します。これは、ツリーに無意味な再描画と多くの追加のレンダリングを強制するため、使用したくない非常に悪い回避策です。
多分私は何か他のものを見逃していますか?
したがって、基本的な質問は次のとおり
です。親ノードを展開せずに子ノードを展開する他の方法はありますか?
ツリーの状態を保存/復元するために使用するいくつかのクラスを以下に示します。
TreeUtils.getTreeState(tree)
JTree の状態を取得し、JTree の状態TreeUtils.setTreeState(tree,treeState)
を復元するために呼び出すだけです。ツリーは 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 );
}
親ノードが展開されている場合にのみ呼び出される子ノードを収集するメソッド。したがって、折りたたまれたノードの下にあるすべての子ノードは無視されます。その動作を変更すると、この質問の冒頭で説明した問題が表示されます-親ノードが展開されます。