2009-03-05 7 views
29

Ho un TreeView che utilizza un HierarchicalDataTemplate per associare i suoi dati.Come ottenere TreeViewItem dall'elemento HierarchicalDataTemplate?

Ecco come si presenta:

<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}> 
    <TreeView.Resources> 
    <HierarchicalDataTemplate 
    DataType="{x:Type local:MyTreeViewItemViewModel}" 
    ItemsSource="{Binding Children}"> 
     <!-- code code code --> 
    </HierarchicalDataTemplate> 
    </TreeView.Resources> 
</TreeView> 

Ora, dal code-behind della dicono che la finestra principale, voglio ottenere la corrente selezionata TreeViewItem. Tuttavia, se uso:

this.mainTreeList.SelectedItem; 

La selectedItem è di tipo MyTreeViewItemViewModel. Ma voglio ottenere il 'genitore' o 'legato' TreeViewItem. Non lo passo al mio oggetto TreeViewItemModel (non saprei nemmeno come).

Come posso fare questo?

+0

@romkyns: Cosa? Questa domanda non ha * * nulla a che fare con il tenere traccia dell'elemento selezionato. –

+0

@romkyns: beh, non dovresti prendere TreeViewItem in primo luogo, quindi non importa se è difficile da fare o meno. –

+0

@ H.B. Potrebbe essere così ... forse [puoi aiutarmi a farlo senza TreeViewItem] (http://stackoverflow.com/questions/9761336/whats-the-wpf-way-to-mark-a-command-as- disponibile solo-se-la-genitore-di-un-albero)? –

risposta

20

Da Bea Stollnitz 's blog su questo, provare a

TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); 
+0

Abbastanza buono per me, grazie. (è un po 'diverso però, CurrentItem restituisce un oggetto e ContainerFromIndex prende un numero intero, quindi ho dovuto usare CurrentIndex o qualcosa del genere, ma oh bene) – Razzie

+0

Ah, ho appena copiato-n-incollato dal blog di Bea e poi ho cambiato alcune cose. Non ho notato quell'errore :) Ancora, aggiornerò la mia risposta. –

+7

'CurrentPosition' è sempre 0 per me, indipendentemente da cosa è selezionato. Il post di blog a cui si fa riferimento è sugli elenchi, non sugli alberi, forse è per questo. Non c'è 'HierarchicalDataTemplate' coinvolto nel post del blog collegato. –

0

Avete bisogno del TreeViewItem perché si sta andando a modificare ciò che viene visualizzato? In tal caso, ti consigliamo di utilizzare uno stile per modificare il modo in cui viene visualizzato l'elemento anziché utilizzare code-behind anziché modificare direttamente TreeViewItem. Si spera che dovrebbe essere più pulito.

30
TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); 

Non funziona (per me) come mainTreeList.Items.CurrentPosition in una vista ad albero utilizzando una HierarchicalDataTemplate sarà sempre -1.

NEITHER di seguito come mainTreeList.Items.CurrentItem in una vista ad albero utilizzando un HierarchicalDataTemplate sarà sempre nullo.

TreeViewItem item = (TreeViewItem)mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromItem(mainTreeList.Items.CurrentItem); 

INVECE ho dovuto impostare un all'ultimo selezionato TreeViewItem nel caso TreeViewItem.Selected indirizzato che bolle fino alla visualizzazione ad albero (il sé del TreeViewItem non esistono in fase di progettazione, come stiamo utilizzando un HierarchicalDataTemplate) .

L'evento può essere catturato in XAML come così:

<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../> 

Poi l'ultima TreeViewItem selezionato può essere impostato in modo da all'evento come:

private void TreeViewItemSelected(object sender, RoutedEventArgs e) 
    { 
     TreeViewItem tvi = e.OriginalSource as TreeViewItem; 

     // set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null) 
     this.lastSelectedTreeViewItem = tvi; 

     ... 
    } 
+0

L'unico modo per CurrentItem di essere uguale a null (o CurrentIndex uguale a -1) è se non ci sono elementi nell'albero. Assicurati che ci siano articoli prima di provare a ottenere il contenitore degli oggetti. –

+0

CurrentItem non è uguale a SelectedItem. ContainerFromItem restituisce null se non è in grado di trovare il contenitore e null può essere eseguito il cast in un oggetto TreeView perfettamente senza doverlo utilizzare come cast. Hai persino eseguito il codice? –

+2

Il problema è che stai usando mainTreeList.Items che funzionerà solo per gli elementi nel primo livello della struttura ad albero - ogni nodo di visualizzazione ad albero (TreeViewItem) ha la propria raccolta di elementi, diversamente dall'esempio di Bea che citi che sta usando un ListBox e ha solo un livello di oggetti. Ne consegue che è necessario ottenere il riferimento TreeViewItem attualmente selezionato per il quale l'unico modo in cui ho trovato di farlo quando si utilizza un HierarchicalDataTemplate è catturare l'evento TreeViewItem.Selected come nella mia risposta. – markmnl

6

mi sono imbattuto in questo stesso problema. Avevo bisogno di arrivare a TreeViewItem in modo da poter essere selezionato. Mi sono reso conto che potevo semplicemente aggiungere una proprietà IsSelected al mio ViewModel, che poi ho associato a TreeViewItems IsSelectedProperty. Ciò può essere ottenuto con l'ItemContainerStyle:

<TreeView> 
      <TreeView.ItemContainerStyle> 
       <Style TargetType="{x:Type TreeViewItem}"> 
        <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> 
        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
       </Style> 
      </TreeView.ItemContainerStyle> 
</TreeView> 

Ora, se voglio avere una voce nel controllo TreeView selezionato, mi basta chiamare IsSelected sulla mia classe ViewModel direttamente.

Spero che aiuti qualcuno.

+2

Secondo me questo è il modo migliore di usare le viste. Gli elementi _need_ sono viewmodels in modo che tu possa lavorare con loro proprietà. Altrimenti si finisce per dover fare ogni genere di shenanigans per ottenere quello che dovrebbe essere un comportamento semplice per funzionare correttamente. –

+1

Sono d'accordo, tuttavia la domanda era come ottenere _TreeViewItem_ non l'oggetto che avvolge. – markmnl

+0

Sì, questo è certamente il modo in cui dovresti farlo. Ma avere un ViewModel per _evvverything_ è piuttosto un PITA, specialmente per le piccole UI di base. –

4

Ispirato dalla risposta di Fëanor, ho tentato di fare il TreeViewItem facilmente accessibile per ogni elemento di dati che un TreeViewItem è stato creato per.

L'idea è quella di aggiungere un campo -typed TreeViewItem al modello vista, esposto anche tramite un'interfaccia, e hanno la TreeView popolare automaticamente ogni volta che si crea il contenitore TreeViewItem.

Questo viene fatto sottoclasse TreeView e allegando un evento allo ItemContainerGenerator, che registra lo TreeViewItem ogni volta che viene creato. I trucchi includono il fatto che i numeri TreeViewItem vengono creati pigramente, quindi potrebbe non essere disponibile in determinati orari.

Da quando ho postato questa risposta, l'ho sviluppata ulteriormente e l'ho usata a lungo in un progetto. Nessun problema finora, a parte il fatto che questo violi MVVM (ma ti risparmia anche un sacco di dati per i casi semplici). Fonte here.

Uso

Selezionare il padre della voce selezionata e comprimerlo, assicurando che è nella vista:

... 
var selected = myTreeView.SelectedItem as MyItem; 
selected.Parent.TreeViewItem.IsSelected = true; 
selected.Parent.TreeViewItem.IsExpanded = false; 
selected.Parent.TreeViewItem.BringIntoView(); 
... 

dichiarazioni:

<Window ... 
     xmlns:tvi="clr-namespace:TreeViewItems" 
     ...> 
    ... 
    <tvi:TreeViewWithItem x:Name="myTreeView"> 
     <HierarchicalDataTemplate DataType = "{x:Type src:MyItem}" 
            ItemsSource = "{Binding Children}"> 
      <TextBlock Text="{Binding Path=Name}"/> 
     </HierarchicalDataTemplate> 
    </tvi:TreeViewWithItem> 
    ... 
</Window> 
class MyItem : IHasTreeViewItem 
{ 
    public string Name { get; set; } 
    public ObservableCollection<MyItem> Children { get; set; } 
    public MyItem Parent; 
    public TreeViewItem TreeViewItem { get; set; } 
    ... 
} 

Codice

public class TreeViewWithItem : TreeView 
{ 
    public TreeViewWithItem() 
    { 
     ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 

    private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     var generator = sender as ItemContainerGenerator; 
     if (generator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      int i = 0; 
      while (true) 
      { 
       var container = generator.ContainerFromIndex(i); 
       if (container == null) 
        break; 

       var tvi = container as TreeViewItem; 
       if (tvi != null) 
        tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 

       var item = generator.ItemFromContainer(container) as IHasTreeViewItem; 
       if (item != null) 
        item.TreeViewItem = tvi; 

       i++; 
      } 
     } 
    } 
} 

interface IHasTreeViewItem 
{ 
    TreeViewItem TreeViewItem { get; set; } 
} 
+0

L'ispezione di questa strada è stata molto interessante e formativa su come funziona wpf (ovvero magia nera), ma la risposta con i doni mi ha spinto a farlo correttamente. Questo non è un buon modo di farlo (con rispetto) perché gli oggetti vengono rigenerati, e tu continui ad aggiungere handler, inoltre non rimuoverai mai quei gestori, perché non c'è uno stato di generatore "rimosso". Abbiamo organizzato eventi e dovremmo apprenderli e usarli. –

+0

@ L.Trabacchin Sarei certamente d'accordo che il mio approccio è hacky. TBH, per le UI complesse la soluzione migliore è quasi certamente quella di abbandonare del tutto l'idea di accedere a TreeViewItems; questo non è il modo in cui dovrebbe essere usato WPF. Dovremmo invece accedere alle nostre istanze di ViewModel. Non ricordo perché la risposta generosa non l'abbia tagliata per me, ma sembra meno hacky della mia. –

+0

Certo, un altro buon modo per farlo è usare i viewmodels, ma dipende da cosa stiamo cercando di ottenere, per me gli eventi indirizzati hanno funzionato meglio perché stavo cercando di estendere la vista ad albero per ottenere una vista ad albero multiselect, ma volevo che fosse avere meno codice possibile, perché ciò potrebbe portare a problemi di manutenzione e bug e volere che funzioni in modo indipendente sul modello utilizzato, l'ho fatto per lo più, ho alcuni difetti in questo momento, esponendo gli elementi selezionati a cui accedere da altri controlli. .. lo vorrei sapere di più :) grazie comunque! –

5
TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0. 

Come su

TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromItem(mainTreeList.SelectedItem))); 

Questo funziona meglio per me.

+0

In realtà è possibile semplificare l'ultimo esempio. Basta usare '... ItemGenerator.ContainerFromItem (mainTreeList.SelectedItem)'. Questo funziona anche se stai usando uno o più 'HierarchicalDataTemplate', mentre il primo esempio no. Mi sono permesso di modificare la tua risposta. Spero che vada bene con te. –

0

Se dovete trovare voce nei bambini di bambini potrebbe essere necessario utilizzare la ricorsione come questo

public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview 
{ 
    if (item == null) 
     return false; 
    TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem; 
    if (child != null) 
    { 
     child.IsSelected = true; 
     return true; 
    } 
    foreach (object c in item.Items) 
    { 
     bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select); 
     if (result == true) 
      return true; 
    } 
    return false; 
} 
0

Ecco soluzione. rtvEsa è ad albero. HierarchicalDataTemplate è il modello treeview Il tag produce il consumo effettivo dell'articolo corrente. Questo non è l'elemento selezionato è l'elemento corrente nel controllo struttura che utilizza HierarchicalDataTemplate.

Items.CurrentItem fa parte della raccolta interna di alberi. Non puoi ottenere molti e diversi dati. Ad esempio, Items.ParenItem.

<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}"> 

    <TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" /> 

2

provare qualcosa di simile:

public bool UpdateSelectedTreeViewItem(PropertyNode dateItem, ItemsControl itemsControl) 
    { 
     if (itemsControl == null || itemsControl.Items == null || itemsControl.Items.Count == 0) 
     { 
      return false; 
     } 
     foreach (var item in itemsControl.Items.Cast<PropertyNode>()) 
     { 
      var treeViewItem = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; 
      if (treeViewItem == null) 
      { 
       continue; 
      } 
      if (item == dateItem) 
      { 
       treeViewItem.IsSelected = true; 
       return true; 
      } 
      if (treeViewItem.Items.Count > 0 && UpdateSelectedTreeViewItem(dateItem, treeViewItem)) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 
Problemi correlati