2013-08-15 15 views
5

Stavo lavorando con lo stato dell'albero (nodi espansi/selezionati) salvando e creando una classe di utilità che può salvare e ripristinare gli stati dei nodi. Funziona bene.Ripristino degli stati del nodo albero espanso/compresso

Ma c'è ancora un problema con JTree stesso - mentre l'utente sta lavorando con qualche istanza JTree (espansione/compressione di nodi) ci potrebbe essere una situazione in cui un nodo (nascosto sotto un altro nodo compresso) viene espanso. Niente di speciale, va bene.

JTree conserva i record dei nodi espansi/compressi in un separato expandedState Hashtable utilizzando il percorso del nodo come chiave e valore booleano come stato espanso. Quindi, quando quel nodo espanso sotto nodo principale compresso diventerà visibile, sarà comunque espanso poiché c'è un record per esso in expandedState Hashtable con valore true.

Situazione spiegato immagini ...
1. Expand radice ed espandere qualche nodo (cartella "glassfish4") sotto root:
enter image description here
2. Collapse radice:
enter image description here
3. espandersi radice e vediamo ancora il nodo figlio (cartella "glassfish4") ampliato:
enter image description here

pensate che io sia salvato stato albero al momento screenshot # 2, quando radice è crollato - il problema è che se voglio ripristinare tutti gli stati dei nodi dell'albero (anche per quelli nascosti) non riesco ad espandere un nodo sotto un altro nodo compresso perché questo costringerà tutti i nodi genitore ad espandersi. Inoltre non posso accedere a expandedState Hashtable per modificare gli stati espansi direttamente all'interno di esso poiché è dichiarato privato in JTree e non ci sono buoni modi per accedervi. Quindi non posso riprodurre completamente lo stato dell'albero iniziale.

quindi quello che posso fare è:

  1. con forza di accesso che Hashtable attraverso la riflessione - pessima idea nodi
  2. Rewrite JTree ampliano la logica - questa è anche una cattiva idea
  3. Ripristinare tutti gli stati espansi prima quindi ripristina tutti gli stati compresso, che obbligherà l'albero a eseguire ulteriori ridisegni inutili e un sacco di rendering aggiuntivo, quindi è una soluzione veramente brutta che non voglio usare

Forse mi manca qualcos'altro?

Quindi la domanda è:
C'è un altro modo per espandere i nodi figlio senza causare l'espansione dei nodi padre?

È possibile trovare alcune classi che utilizzo per salvare/ripristinare lo stato dell'albero di seguito.

È sufficiente chiamare TreeUtils.getTreeState(tree) per recuperare lo stato JTree e TreeUtils.setTreeState(tree,treeState) per ripristinare lo stato JTree. Si noti che l'albero deve utilizzare UniqueNode, altrimenti tali metodi genereranno ClassCastException - è possibile semplicemente sostituire DefaultMutableTreeNode con UniqueNode se si dispone di nodi propri che estendono DefaultMutableTreeNode.

UniqueNode.java - semplice nodo con il proprio ID univoco

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 - classe di utilità che salva/carichi TreeState da/in 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 - classe contenitore per la mappa che contiene gli stati dei nodi

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 - espansione singolo nodo/selezione stato

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; 
    } 
} 

proposito, setTreeState metodo evita che ristabilisce stati espansi sotto nodi collassati al momentaneamente

 // 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); 
     } 

metodo che blocca nodi figli solo chiamato se il nodo genitore è espanso. Quindi tutti i nodi figli sotto nodi compressi vengono ignorati. Se cambi questo comportamento, vedrai il problema che ho descritto all'inizio di questa domanda: i nodi principali verranno espansi.

+0

+1 prega è possibile raggiungere quegli eventi, traccia da TreeModelListener, utilizzando TreeExpansionListener e TreeWillExpandListener (o bug nel L & F, problemi standard con JList, JComboBox, JSpinner e JTree e la sua Model_To_View) – mKorbel

+0

buona domanda - difficile rispondere : afair, anche il JTree è solo uno schiavo dello stato di espansione, il vero controller è la AbstractLayoutCache creata e utilizzata dal delegato ui. Quindi sospetto (mai provato) che una soluzione reale coinvolga un layoutCache personalizzato che richiede uis personalizzati ... – kleopatra

+0

@mKorbel è davvero un'opzione per ascoltare l'espansione dell'albero ed espandere i nodi nascosti quando diventeranno visibili, ma quello è un soluzione alternativa: una migliore, ma ancora una soluzione alternativa. Quello che voglio è ripristinare lo stato dell'albero allo stesso tempo senza lasciare tracce (come ascoltatori di espansione pigri) di presenza di "strumenti" di ripristino. –

risposta

1

Perché non ripristinare lo stato eseguendo le stesse azioni come descritto, prima impostare i sottonodi da espandere, quindi impostare il nodo principale in modo che sia compresso come necessario?

L'unica differenza rispetto al codice corrente è l'utilizzo di due iterazioni anziché uno. Innanzitutto, esegui l'iterazione e espandi dove desideri, quindi esegui iterazioni e collassi dove desiderato.

L'albero dovrebbe dipingere una sola volta in ogni caso a causa della logica del ridisegno.

+0

Ho detto nel post di domanda "perché no?": "Ripristina tutti gli stati espansi prima di ripristinare tutti gli stati compressi - che obbligherà l'albero a fare ulteriori ripetizioni inutili e un sacco di rendering aggiuntivo, quindi questo è una soluzione davvero brutta che non voglio usare. " Potrebbe (e sarà su un grande albero con molte espansioni) ridipingere un paio di volte e questo è ciò che non voglio accadere. Questa soluzione è davvero pessima se utilizzerete una grande applicazione con una struttura ad albero enorme. –

+0

Come ho sottolineato, non vi è alcun impatto sul rendering. Il JTree invocherà il ridisegno che non farà altro che programmare una vernice, ma tutte le vernici programmate saranno costruite in un'unica operazione di pittura. Potrebbe esserci un sovraccarico quando JTree tenta di calcolare le regioni interessate che possono essere evitate chiamando 'RepaintManager.currentManager (tree) .addDirtyRegion (tree, 0, 0, tree.getWidth(), tree.getHeight())' a segna l'intero albero come sporco, che semplifica ogni ulteriore calcolo. – Holger

+0

Non appena espongo o comprimo qualsiasi nodo - albero pianifica un ridisegno. Repaint Manager unirà quelli ridisposti, ma solo se tutte le azioni di espansione e compressione vengono eseguite entro 1 chiamata a EDT. Nel mio caso i child asincroni si stanno caricando e si espandono/comprimono le chiamate verranno effettuate entro alcune chiamate invokeLater (non appena vengono caricati i nodi figli richiesti) e il repaint si verificherà tra quelle chiamate. Spero che ciò chiarisca il caso: non volevo rendere complicato l'esempio aggiungendo il caricamento del nodo asincrono non necessario poiché non influisce sulla logica di base per questo caso. –

Problemi correlati