2012-02-04 11 views
12

Ktoś zasugerował użycie WPF TreeView i pomyślałem: "Tak, to wygląda na właściwe podejście." Teraz, kilka godzin później, po prostu nie mogę uwierzyć, jak trudno było użyć tej kontroli. Dzięki kilku badaniom udało mi się sprawić, że kontrolka TreeView działa, ale po prostu nie mogę znaleźć "właściwego" sposobu na przeniesienie wybranego elementu do modelu widoku. Nie muszę ustawiać wybranego elementu z kodu; Potrzebuję tylko mojego modelu widoku, aby wiedzieć, który element został wybrany przez użytkownika.Pobierz wybrane drzewoViewItem przy użyciu MVVM

Do tej pory mam ten XAML, który nie jest bardzo intuicyjny sam w sobie. To wszystko w tagu 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> 

A oto katalogów:

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

to poprawnie pokazuje serwerów przez środowiska (dev, QA, prod). Jednak znalazłem różne sposoby, aby uzyskać wybrany element, a wiele z nich jest zawiłych i trudnych. Czy istnieje prosty sposób, aby uzyskać wybrany element do mojego modelu widoku? prosty

Uwaga: istnieje właściwość SelectedItem w TreeView`, ale jest ona tylko do odczytu. Frustruje mnie to, że tylko do odczytu jest w porządku; Nie chcę tego zmieniać za pomocą kodu. Ale nie mogę go użyć, ponieważ kompilator narzeka, że ​​jest tylko do odczytu.

Był też pozornie elegancki sugestia zrobić coś takiego:

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

I zadał to pytanie: „Jak twój model widok może uzyskać te informacje uzyskać że ContentPresenter posiada wybrany element, ale w jaki sposób przekazujemy to do modelu widoku? " Ale nie ma jeszcze odpowiedzi.

Moje ogólne pytanie brzmi: "Czy jest jakiś prosty sposób, aby uzyskać wybrany element do mojego modelu widoku?"

Odpowiedz

27

Aby zrobić to, co chcesz, możesz zmodyfikować ItemContainerStyle z TreeView:

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

Twój widok model (ten widok model dla każdego elementu w drzewie), to musi wystawiać logiczną IsSelected nieruchomości.

Jeśli chcesz być w stanie kontrolować, czy konkretnego TreeViewItem jest rozwinięty można użyć setter dla tej nieruchomości TOO:

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

Państwa zdanie model ma wtedy wystawiać logiczną IsExpanded nieruchomości.

Należy zauważyć, że te właściwości działają w obie strony, więc jeśli użytkownik wybierze węzeł w drzewie, właściwość IsSelected modelu widoku zostanie ustawiona na wartość true. Z drugiej strony, jeśli ustawisz IsSelected na wartość true w modelu widoku, wybrany zostanie węzeł w drzewie dla tego modelu widoku. I podobnie z rozszerzeniem.

Jeśli nie masz modelu widoku dla każdego elementu w drzewie, dobrze, to powinieneś go zdobyć. Brak modelu widoku oznacza, że ​​używasz obiektów modelu jako modeli widoku, ale aby to działało, te obiekty wymagają właściwości IsSelected.

Aby wystawiać właściwość SelectedItem na swojej macierzystej widok model (jeden zwiążesz na TreeView i że posiada kolekcję dziecko obejrzeniu modeli) można wdrożyć go tak:

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

Jeśli nie chcesz śledzić wyboru poszczególnych elementów na drzewie, możesz nadal używać właściwości SelectedItem na TreeView. Jednak aby móc to zrobić "w stylu MVVM", musisz użyć zachowania Blend (dostępnego jako różne pakiety NuGet - wyszukaj "interaktywność mieszania").

Tutaj mam dodał EventTrigger który wykonuje polecenie każdym razem wybranych zmian pozycji w drzewie:

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

Trzeba będzie dodać obiekt SetSelectedItemCommand na DataContext z TreeView zwrócenie ICommand. Kiedy wybrana pozycja w widoku drzewa zmienia się, metoda Execute jest wywoływana z wybraną pozycją jako parametrem. Najłatwiejszym sposobem utworzenia polecenia jest prawdopodobnie użycie wersji DelegateCommand (google to, aby uzyskać implementację, ponieważ nie jest częścią WPF).

Być może lepszą alternatywą, która pozwala na dwukierunkowe wiązanie bez polecenia clunky, jest użycie BindableSelectedItemBehavior dostarczonego przez Steve'a Greatrexa tutaj w Stack Overflow.

+0

Ale czy model widoku nie ma powiązania z IsSelected? W jaki sposób otrzymuje wartość? –

+0

I według wartości, mam na myśli wartość wybranego elementu. Nie chcę tylko wiedzieć, czy coś jest wybrane, chcę poznać wartość wybranego przedmiotu. –

+0

Po prostu zauważyłem, że napisałeś to: "(model widoku dla każdego elementu w drzewie)." Nie mam modelu widoku dla każdego elementu w drzewie. Każda pozycja w drzewie jest pozycją na liście * jeden * w modelu * jeden * widok. –

4

Prawdopodobnie wykorzystam zdarzenie SelectedItemChanged do ustawienia odpowiedniej właściwości na maszynie wirtualnej.

+0

Czy nie potrzebowałbym obsługi kodowej do obsługi zdarzenia? Próbuję być czysty i nie mam kodu w plikach zza kodu. –

+2

@BobHorn: Niekoniecznie, ale ludzie mają zbyt obsesję na punkcie kodu za tak, to nie jest tak wielka sprawa ... –

+0

Tak, prawdopodobnie masz rację, ale niech mnie diabli, jeśli pozwolę temu drzewu śmieci być punktem przełomowym ... lol. –

1

Na podstawie odpowiedzi Martina złożyłem prostą aplikację pokazującą, jak zastosować proponowane rozwiązanie.

Przykładowy kod wykorzystuje platformę Cinch V2 do obsługi MVVM, ale można go łatwo zmienić, korzystając z preferencji użytkownika.

Dla zainteresowanych here is the code na GitHub

Nadzieję, że to pomaga.

0

Nieco późno do partii, ale dla tych, którzy idą w poprzek to teraz, moje rozwiązanie było:

  1. Dodaj odwołanie do „System.Windows.Interactivity”
  2. Dodaj poniższy kod do katalogów element. <i:Interaction.Triggers> <i:EventTrigger EventName="SelectedItemChanged"> <i:InvokeCommandAction Command="{Binding SomeICommand}" CommandParameter="{Binding ElementName=treeviewName, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers>

To pozwoli Ci korzystać ze standardowego MVVM ICommand wiążących aby uzyskać dostęp do SelectedItem bez konieczności użycia kodu tyłu lub trochę długo zdyszany obejść.

Powiązane problemy