2012-02-04 13 views
12

Quindi qualcuno ha suggerito di utilizzare un WPF TreeView e ho pensato: "Sì, sembra l'approccio giusto". Ora, ore e ore dopo, semplicemente non riesco a credere a quanto sia stato difficile usare questo controllo. Attraverso una serie di ricerche, sono riuscito a far funzionare il controllo TreeView`, ma semplicemente non riesco a trovare il modo "corretto" per ottenere l'elemento selezionato nel modello di visualizzazione. Non ho bisogno di impostare l'elemento selezionato dal codice; Ho solo bisogno del mio modello di visualizzazione per sapere quale elemento l'utente ha selezionato.Ottieni AlberoViewView selezionato utilizzando MVVM

Finora, ho questo XAML, che non è molto intuitivo da solo. Questo è tutto all'interno del tag UserControl.Resources:

<CollectionViewSource x:Key="cvs" Source="{Binding ApplicationServers}"> 
    <CollectionViewSource.GroupDescriptions> 
     <PropertyGroupDescription PropertyName="DeploymentEnvironment"/> 
    </CollectionViewSource.GroupDescriptions> 
</CollectionViewSource> 

<!-- Our leaf nodes (server names) --> 
<DataTemplate x:Key="serverTemplate"> 
    <TextBlock Text="{Binding Path=Name}"/> 
</DataTemplate> 

<!-- Note: The Items path refers to the items in the CollectionViewSource group (our servers). 
      The Name path refers to the group name. --> 
<HierarchicalDataTemplate x:Key="categoryTemplate" 
          ItemsSource="{Binding Path=Items}" 
          ItemTemplate="{StaticResource serverTemplate}"> 
    <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/> 
</HierarchicalDataTemplate> 

Ed ecco la vista ad albero:

<TreeView DockPanel.Dock="Bottom" ItemsSource="{Binding Source={StaticResource cvs}, Path=Groups}" 
       ItemTemplate="{StaticResource categoryTemplate}"> 
      <Style TargetType="TreeViewItem"> 
       <Setter Property="IsSelected" Value="{Binding Path=IsSelected}"/> 
      </Style> 
     </TreeView> 

Questa mostra correttamente i server per l'ambiente (dev, QA, prod). Tuttavia, ho trovato vari modi su SO per ottenere l'elemento selezionato, e molti sono contorti e difficili. C'è un modo semplice per ottenere l'elemento selezionato sul mio modello di visualizzazione?

Nota: esiste una proprietà SelectedItem in TreeView`, ma è di sola lettura. Ciò che è frustrante per me è che la sola lettura va bene; Non voglio cambiarlo via codice. Ma non posso usarlo perché il compilatore si lamenta che è di sola lettura.

C'era anche un apparentemente elegante suggerimento per fare qualcosa di simile:

<ContentPresenter Content="{Binding ElementName=treeView1, Path=SelectedItem}" /> 

E ho fatto questa domanda:? "Come può il vostro un modello di vista ottenere queste informazioni ottengo che ContentPresenter tiene l'elemento selezionato, ma come si arriva al modello di vista? " Ma non c'è ancora risposta.

Quindi, la mia domanda generale è: "C'è un modo semplice per ottenere l'elemento selezionato sul mio modello di visualizzazione?"

risposta

27

Per fare ciò che si desidera è possibile modificare la ItemContainerStyle del TreeView:

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

tuo vista del modello-(il modello vista per ogni elemento della struttura), poi deve esporre un valore booleano IsSelected proprietà.

Se si vuole essere in grado di controllare se un particolare TreeViewItem è espanso è possibile utilizzare un setter per quella proprietà anche:

<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> 

La vista-modello ha poi ad esporre un valore booleano IsExpanded proprietà.

Si noti che queste proprietà funzionano in entrambi i modi, pertanto se l'utente seleziona un nodo nell'albero, la proprietà IsSelected del modello di visualizzazione verrà impostata su true. D'altra parte se si imposta IsSelected su true su un modello di vista, verrà selezionato il nodo nell'albero per tale modello di vista. E allo stesso modo con espanso.

Se non si dispone di un modello di visualizzazione per ciascun elemento nell'albero, beh, quindi si dovrebbe ottenere uno. Non avere un modello di visualizzazione significa che si stanno utilizzando gli oggetti del modello come modelli di visualizzazione, ma per far funzionare questi oggetti è necessaria una proprietà IsSelected.

Per esporre una proprietà SelectedItem sulla vista del modello-parent (quella che si legano al TreeView e che ha una collezione di bambino view-modelli) è possibile implementare in questo modo:

public ChildViewModel SelectedItem { 
    get { return Items.FirstOrDefault(i => i.IsSelected); } 
} 

Se non si desidera tenere traccia della selezione su ogni singolo oggetto sull'albero, è comunque possibile utilizzare la proprietà SelectedItem su TreeView. Tuttavia, per essere in grado di farlo in "stile MVVM" è necessario utilizzare un comportamento di miscelazione (disponibile come vari pacchetti NuGet - ricerca di "interattività blend").

Qui ho aggiunto un EventTrigger che richiamare un comando ogni volta che cambia voce selezionata nella struttura:

<TreeView x:Name="treeView"> 
    <i:Interaction.Triggers> 
    <i:EventTrigger EventName="SelectedItemChanged"> 
     <i:InvokeCommandAction 
     Command="{Binding SetSelectedItemCommand}" 
     CommandParameter="{Binding SelectedItem, ElementName=treeView}"/> 
    </i:EventTrigger> 
    </i:Interaction.Triggers> 
</TreeView> 

si dovrà aggiungere una proprietà SetSelectedItemCommand sul DataContext del TreeView restituzione di un ICommand. Quando l'elemento selezionato della vista ad albero cambia il metodo Execute sul comando viene chiamato con l'elemento selezionato come parametro. Il modo più semplice per creare un comando è probabilmente quello di utilizzare uno DelegateCommand (google per ottenere un'implementazione in quanto non fa parte di WPF).

Un'alternativa forse migliore che consente l'associazione bidirezionale senza il comando clunky è di utilizzare BindableSelectedItemBehavior fornito da Steve Greatrex qui su Stack Overflow.

+0

Ma il modello di vista non ha solo un legame con IsSelected? Come ottiene effettivamente il valore? –

+0

E per valore, intendo il valore dell'elemento selezionato. Non sto cercando solo di sapere se qualcosa è selezionato, voglio sapere il valore dell'oggetto selezionato. –

+0

Quindi, ho appena notato che hai scritto questo: "(il modello di visualizzazione per ogni oggetto nell'albero)." Non ho un modello di vista per ogni oggetto nell'albero. Ogni elemento nell'albero è un elemento in * un * elenco nel modello * one * view. –

4

Probabilmente userei l'evento SelectedItemChanged per impostare una rispettiva proprietà sul VM.

+0

Non avrei bisogno di usare code-behind per gestire l'evento? Sto cercando di essere puro su questo e non ho alcun codice nei file code-behind finora. –

+2

@BobHorn: Non necessariamente, ma le persone sono troppo ossessionate dal codice, comunque, non è un grosso problema ... –

+0

Sì, probabilmente hai ragione, ma sarò dannato se lascerò questa vista ad albero spazzatura essere il punto di rottura ... lol. –

1

Sulla base della risposta di Martin ho presentato una semplice applicazione che mostra come applicare la soluzione proposta.

Il codice di esempio utilizza il framework Cinch V2 per supportare MVVM ma può essere facilmente modificato per utilizzare il framework di preferenza.

Per chi fosse interessato, here is the code su GitHub

Speranza che aiuta.

0

Un po 'in ritardo alla festa, ma per coloro che stanno arrivando in questo momento, la mia soluzione era:

  1. Aggiungere un riferimento a 'System.Windows.Interactivity'
  2. Aggiungere il seguente codice nella tua treeview elemento. <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>

Questo vi permetterà di utilizzare un MVVM ICommand norma vincolante per accedere al SelectedItem senza dover utilizzare codice dietro o qualche lavoro lungo senza fiato intorno.

Problemi correlati