2012-06-09 2 views
10

Istnieje kilka postów dyskusji o dodaniu zdolności wiązania danych dla ListView.SelectedItems z nietrywialną ilością kodu. W moim scenariuszu nie muszę ustawiać go z poziomu ViewModel, tylko pobierając wybrane elementy w celu wykonania na nich akcji i jest uruchamiany przez polecenie, więc aktualizacja push też nie jest konieczna.Uzyskiwanie list WPF. Wybierane elementy w ViewModel

Czy istnieje proste rozwiązanie (pod względem linii kodu), być może z tyłu kodu? Mam problemy z kodowaniem, dopóki View i ViewModel nie muszą się ze sobą nawzajem odwoływać. Myślę, że jest to bardziej ogólne pytanie: "najlepsza praktyka dla VM, aby uzyskać dane z widoku na żądanie", ale nie mogę niczego znaleźć ...

Odpowiedz

23

Aby uzyskać SelectedItems tylko po wykonaniu polecenia, użyj CommandParameter i przeprowadź ListView.SelectedItems.

<ListBox x:Name="listbox" ItemsSource="{Binding StringList}" SelectionMode="Multiple"/> 
<Button Command="{Binding GetListItemsCommand}" CommandParameter="{Binding SelectedItems, ElementName=listbox}" Content="GetSelectedListBoxItems"/> 
+4

'SelectedItems' (liczba mnoga) nie obsługuje wiązania danych. Zobacz [ten link] (http://stackoverflow.com/questions/803216/managing-multiple-selections-with-vvm) i [ten link] (http://social.msdn.microsoft.com/forums/en- US/wpf/thread/edd335ea-e5e1-48e1-91a2-793d613f5cc3 /). Nie działa również jako 'CommandParameter', zawsze otrzymuję' null', podczas gdy użycie 'SelectedItem' (w liczbie pojedynczej) jest w porządku. –

+0

@ user986080 Nie zdawałem sobie sprawy, że 'SelectedItems' nie obsługuje wiązania. Usunąłem to z odpowiedzi. Jednak parametr 'CommandParameter' działa, przetestowałem go i udało mi się wyświetlić listę wybranych elementów. – evanb

+0

Mój XAML przykład pokazuje 'ListBox', ale również testowałem' ListView' i był w stanie uzyskać wybrane elementy z parametru polecenia. – evanb

2

Nie sądzę, że to prawda warunek uznania, że ​​"Zobacz i ViewModel nie muszą się znać"; W widoku MVVM zawsze wiesz o ViewModel.

Natrafiłem też na taką sytuację, w której musiałem uzyskać dostęp do ViewModel w kodzie widoku, a następnie zapełnić niektóre dane (np. Wybrane pozycje), co jest konieczne podczas korzystania z kontrolek stron trzecich, takich jak ListView, DataGrid itp.

Jeśli bezpośrednie powiązanie właściwości maszyny wirtualnej nie jest możliwe, powinienem wysłuchać zdarzenia ListViw.SelectionChanged, a następnie zaktualizować właściwość ViewModels SelectedItems w tym zdarzeniu.

Aktualizacja:

Aby umożliwić VM dane pobiera z widzenia, można narazić interfejs na widoku, który obsługuje Zobacz specyficzną funkcjonalność i ViewModel będą miały odniesienie swojego Widok przez ten interfejs; Korzystanie z interfejsu nadal pozwala na znaczne oddalenie View i ViewModel, ale generalnie nie wolę tego.

MVVM, providing the Association of View to ViewModel

bym nadal preferują approch z obsługi zdarzeń w widoku i zachować VM aktualizowane (z wybranych przedmiotów), w ten sposób VM nie trzeba się martwić o pociągnięcie do danych przed wykonaniem jakiejkolwiek operacji , wystarczy użyć dostępnych danych (ponieważ zawsze będą one aktualizowane jeden).

+0

Przepraszam, że nie było jasne. Nie znając się nawzajem, mam na myśli, że jeden nie odnosi się do drugiego. Również rejestracja w zdarzeniu 'SelectionChanged' nie jest dokładnie konieczna, ponieważ ViewModel potrzebuje tylko pobrać wybrane elementy, gdy polecenie jest wykonywane. To bardziej przypomina "Jak VM może pobierać dane z widoku na żądanie". –

8

Można to osiągnąć za pomocą interakcji wyzwala jak poniżej

  1. trzeba będzie dodać odniesienie do

    Microsoft.Expression.Interactions System.Windows.Interaktywność

Dodaj poniżej xmlns do XAML

xmlns:i="http://schemas.microsoft.com/expression//2010/interactivity" 
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 

Dodaj poniższy kod tylko wewnątrz tagu GridView

<GridView x:Name="GridName"> 
<i:Interaction.Triggers> 
    <i:EventTrigger EventName="SelectionChanged"> 
     <i:InvokeCommandAction Command="{Binding Datacontext.SelectionChangedCommand, ElementName=YourUserControlName}" CommandParameter="{Binding SelectedItems, ElementName=GridName}" /> 
    </i:EventTrigger> 
</i:Interaction.Triggers> 

kod wewnątrz ViewModel zadeklarować nieruchomość poniżej

public DelegateCommand<object> SelectionChangedCommand {get;set;} 

zasięgu zbudować lub ViewModel zainicjować polecenia jak poniżej

SelectionChangedCommand = new DelegateCommand<object> (items => { 
    var itemList = (items as ObservableCollection<object>).Cast<YourDto>().ToList(); 
} 
+0

Miałem takie samo obejście, – Mihai

2

mogę was zapewnić: selectedItems jest rzeczywiście Bindable jako XAML CommandParameter

Po wielu kopanie i googlowania, mam w końcu znalazł proste rozwiązanie do tego wspólnego problemu.

Aby to zadziałało musisz przestrzegać wszystkich poniższych zasad:

  1. Po Ed Ball's suggestion”On You XAML polecenia wiązania z danymi, należy zdefiniować CommandParameter nieruchomości przed Komendy nieruchomości. Jest to bardzo czasochłonny błąd.

  2. Upewnij się ICommand „s CanExecute i Execute metody mają parametr obiektu typu. W ten sposób można zapobiec rzutowaniu wyjątków, które występują, gdy typ databinding CommandParameter nie pasuje do typu parametru metody polecenia.

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems) 
    { 
        // Your goes heres 
    } 
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems) 
    { 
        // Your goes heres 
    } 
    

Na przykład, można wysłać ListView/ListBox za SelectedItems własność wam ICommand metod lub ListView/listbox to samo. Świetnie, prawda?

Nadzieję zapobiega kogoś spędzając mnóstwo czasu zrobiłem, aby dowiedzieć się jak otrzymać selectedItems jak CanExecute parametru.

0

Ponieważ żaden z pozostałych odpowiedzi pomógł mi (za pomocą SelectedItems jak CommandParameter był zawsze null), tutaj jest rozwiązanie dla Universal platforma Windows (UWP) aplikacji. Działa z użyciem Microsoft.Xaml.Interactivity i .

Oto Widok:

<ListView x:Name="ItemsList"> 
    <Interactivity:Interaction.Behaviors> 
     <Core:EventTriggerBehavior EventName="SelectionChanged"> 
      <Core:InvokeCommandAction Command="{x:Bind ViewModel.SelectedItemsChanged}" /> 
     </Core:EventTriggerBehavior> 
    </Interactivity:Interaction.Behaviors> 
    <!-- content etc. --> 
</ListView> 

Oto ViewModel (RelayCommand jest klasa z MVVM Światła):

private List<YourType> _selectedItems = new List<YourType>(); 

private RelayCommand<SelectionChangedEventArgs> _selectedItemsChanged; 
public RelayCommand<SelectionChangedEventArgs> SelectedItemsChanged 
{ 
    get 
    { 
     if (_selectedItemsChanged == null) 
      _selectedItemsChanged = new RelayCommand<SelectionChangedEventArgs>((selectionChangedArgs) => 
      { 
       // add a guard here to immediatelly return if you are modifying the original collection from code 

       foreach (var item in selectionChangedArgs.AddedItems) 
        _selectedItems.Add((YourType)item); 

       foreach (var item in selectionChangedArgs.RemovedItems) 
        _selectedItems.Remove((YourType)item); 
      }); 
     return _selectedItemsChanged; 
    } 
} 

Pamiętaj, że jeśli masz zamiar usunąć elementy z oryginalnej kolekcji po wybór jest zakończony (użytkownik naciska przycisk itp.), usunie również pozycje z listy _selectedItems! Jeśli zrobisz to w pętli foreach, dostaniesz InvalidOperationException. Aby tego uniknąć, wystarczy dodać strażnika w zaznaczonym miejscu jak:

if (_deletingItems) 
    return; 

a następnie w sposobie, w którym na przykład usunięcia pozycji, wykonaj następujące czynności:

_deletingItems = true; 
foreach (var item in _selectedItems) 
    YourOriginalCollection.Remove(item); 
_deletingItems = false;