2009-08-19 9 views
20

Mam listę wyboru wielokrotnego wyboru w aplikacji SL3 przy użyciu pryzmatu i potrzebuję kolekcji w moim modelu wyświetlania, która zawiera aktualnie wybrane elementy w polu listy.Sync SelectedItems w muliselect listbox z kolekcją w ViewModel

Viewmodel nie wie nic o widoku, więc nie ma dostępu do kontrolki listbox. Ponadto muszę mieć możliwość wyczyszczenia wybranych elementów w polu listy z viewmodel.

Nie wiem jak podejść do tego problemu

dzięki Michael

Odpowiedz

40

Tak, że masz ViewModel o następujących właściwościach:

public ObservableCollection<string> AllItems { get; private set; } 
public ObservableCollection<string> SelectedItems { get; private set; } 

Można by zacząć od wiążący swoją kolekcję AllItems do ListBoks:

<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" /> 

Problem polega na tym, że właściwość SelectedItems w ListBox nie jest DependencyProperty. Jest to bardzo złe, ponieważ nie można powiązać go z czymś w swoim ViewModelu.

Pierwsze podejście jest po prostu umieścić tę logikę w opóźnieniem kodu, aby dostosować ViewModel:

public MainPage() 
{ 
    InitializeComponent(); 

    MyListBox.SelectionChanged += ListBoxSelectionChanged; 
} 

private static void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    var listBox = sender as ListBox; 
    if(listBox == null) return; 

    var viewModel = listBox.DataContext as MainVM; 
    if(viewModel == null) return; 

    viewModel.SelectedItems.Clear(); 

    foreach (string item in listBox.SelectedItems) 
    { 
     viewModel.SelectedItems.Add(item); 
    } 
} 

Podejście to będzie działać, ale to jest naprawdę brzydka. Moim preferowanym podejściem jest wyodrębnienie tego zachowania w "Attached Behavior". Jeśli to zrobisz, możesz całkowicie wyeliminować swój kod z tyłu i ustawić go w XAML. Bonus jest, że to „Przydzielony Zachowanie” jest teraz ponownie wykorzystywane w każdym ListBox:

<ListBox ItemsSource="{Binding AllItems}" Demo:SelectedItems.Items="{Binding SelectedItems}" SelectionMode="Multiple" /> 

A oto kod dla podłączonej Zachowanie:

public static class SelectedItems 
{ 
    private static readonly DependencyProperty SelectedItemsBehaviorProperty = 
     DependencyProperty.RegisterAttached(
      "SelectedItemsBehavior", 
      typeof(SelectedItemsBehavior), 
      typeof(ListBox), 
      null); 

    public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached(
      "Items", 
      typeof(IList), 
      typeof(SelectedItems), 
      new PropertyMetadata(null, ItemsPropertyChanged)); 

    public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); } 
    public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; } 

    private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var target = d as ListBox; 
     if (target != null) 
     { 
      GetOrCreateBehavior(target, e.NewValue as IList); 
     } 
    } 

    private static SelectedItemsBehavior GetOrCreateBehavior(ListBox target, IList list) 
    { 
     var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior; 
     if (behavior == null) 
     { 
      behavior = new SelectedItemsBehavior(target, list); 
      target.SetValue(SelectedItemsBehaviorProperty, behavior); 
     } 

     return behavior; 
    } 
} 

public class SelectedItemsBehavior 
{ 
    private readonly ListBox _listBox; 
    private readonly IList _boundList; 

    public SelectedItemsBehavior(ListBox listBox, IList boundList) 
    { 
     _boundList = boundList; 
     _listBox = listBox; 
     _listBox.SelectionChanged += OnSelectionChanged; 
    } 

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     _boundList.Clear(); 

     foreach (var item in _listBox.SelectedItems) 
     { 
      _boundList.Add(item); 
     } 
    } 
} 
+0

to nie działa z SL3, czy to działało dla kogoś innego? – Neil

+1

to nie działa w ogóle, nie tylko dlatego, że klasa powinna być statyczna, wybrane pozycje zawsze mają wartość NULL. – msfanboy

+0

Działa to w SL3, SL4 i WPF. Używam tej metody przez cały czas. Tak, klasa przechowująca przywiązane zachowanie powinna być statyczna. Jest to część wzorca "Attached Behaviour" w SL i WPF. –

3

Dzięki za to! Dodałem niewielką aktualizację do obsługi początkowego ładowania i zmiany DataContext.

Cheers,

Alessandro Pilotti [MVP/IIS]

public class SelectedItemsBehavior 
{ 
    private readonly ListBox _listBox; 
    private readonly IList _boundList; 

    public ListBoxSelectedItemsBehavior(ListBox listBox, IList boundList) 
    { 
     _boundList = boundList; 
     _listBox = listBox; 

     SetSelectedItems(); 

     _listBox.SelectionChanged += OnSelectionChanged; 
     _listBox.DataContextChanged += ODataContextChanged; 
    } 

    private void SetSelectedItems() 
    { 
     _listBox.SelectedItems.Clear(); 

     foreach (object item in _boundList) 
     { 
      // References in _boundList might not be the same as in _listBox.Items 
      int i = _listBox.Items.IndexOf(item); 
      if (i >= 0) 
       _listBox.SelectedItems.Add(_listBox.Items[i]); 
     } 
    } 

    private void ODataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     SetSelectedItems(); 
    } 

    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     _boundList.Clear(); 

     foreach (var item in _listBox.SelectedItems) 
     { 
      _boundList.Add(item); 
     } 
    } 
} 
+0

to tylko ja lub czy zdarzenie DataContextChanged nie istnieje na ListBox – Neil

+0

Robi w WPF, ale nie Silverlight do Silverlight 5. –

1

powyżej Oryginalny rozwiązanie działa, jeśli pamiętać, aby utworzyć wystąpienie kolekcji obserwowalnym pierwszy! Ponadto należy upewnić się, że typ zawartości kolekcji Observable jest zgodny z typem zawartości dla obiektu ListBox ItemSource (jeśli odstępujesz od dokładnego przykładu wymienionego powyżej).

0

Rozwiązaniem dla mnie było połączenie aktualizacji Alessandro Pilotti z zachowaniem Briana Genisio. Ale usuń kod dla DataContext zmieniając Silverlight 4 nie obsługuje tego.

Jeśli wiążisz listbox do ObservableCollection<string> powyższe działa dobrze, ale jeśli wiążą złożone obiekty, takie jak ObservableCollection<Person> SelectedItems { get; private set; } za pośrednictwem DataTemplate, nie wydaje się działać. Jest to spowodowane domyślną implementacją metody równa się, której używa kolekcja. Możesz rozwiązać ten problem, mówiąc obiektowi swojej osoby, które pola porównać, gdy określasz, czy obiekty są równe, odbywa się to poprzez implementację interfejsu IEquatable<T> na twoim obiekcie.

Po tym IndexOf (pozycja) kod będzie działać i móc porównać jeśli obiekty są równe, a następnie wybierz pozycję na liście

// References in _boundList might not be the same as in _listBox.Items 
int i = _listBox.Items.IndexOf(item); 
if (i >= 0) 
    _listBox.SelectedItems.Add(_listBox.Items[i]); 

patrz link: http://msdn.microsoft.com/en-us/library/ms131190(VS.95).aspx

5

Chciałem mieć prawdziwe dwukierunkowe wiązanie, tak aby wybór ListBox odzwierciedlał elementy zawarte w kolekcji SelectedItems podstawowego ViewModel. To pozwala mi kontrolować wybór przez logikę w warstwie ViewModel.

Oto moje modyfikacje do klasy SelectedItemsBehavior. Synchronizują kolekcję ListBox.SelectedItems z podstawową właściwością ViewModel, jeśli właściwość ViewModel implementuje INotifyCollectionChanged (np. Zaimplementowana przez typ ObservableCollection <T>).

public static class SelectedItems 
    { 
    private static readonly DependencyProperty SelectedItemsBehaviorProperty = 
     DependencyProperty.RegisterAttached(
      "SelectedItemsBehavior", 
      typeof(SelectedItemsBehavior), 
      typeof(ListBox), 
      null); 

    public static readonly DependencyProperty ItemsProperty = DependencyProperty.RegisterAttached(
      "Items", 
      typeof(IList), 
      typeof(SelectedItems), 
      new PropertyMetadata(null, ItemsPropertyChanged)); 

    public static void SetItems(ListBox listBox, IList list) { listBox.SetValue(ItemsProperty, list); } 
    public static IList GetItems(ListBox listBox) { return listBox.GetValue(ItemsProperty) as IList; } 

    private static void ItemsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var target = d as ListBox; 
     if (target != null) 
     { 
     AttachBehavior(target, e.NewValue as IList); 
     } 
    } 

    private static void AttachBehavior(ListBox target, IList list) 
    { 
     var behavior = target.GetValue(SelectedItemsBehaviorProperty) as SelectedItemsBehavior; 
     if (behavior == null) 
     { 
     behavior = new SelectedItemsBehavior(target, list); 
     target.SetValue(SelectedItemsBehaviorProperty, behavior); 
     } 
    } 
    } 

    public class SelectedItemsBehavior 
    { 
    private readonly ListBox _listBox; 
    private readonly IList _boundList; 

    public SelectedItemsBehavior(ListBox listBox, IList boundList) 
    { 
     _boundList = boundList; 
     _listBox = listBox; 
     _listBox.Loaded += OnLoaded; 
     _listBox.DataContextChanged += OnDataContextChanged; 
     _listBox.SelectionChanged += OnSelectionChanged; 

     // Try to attach to INotifyCollectionChanged.CollectionChanged event. 
     var notifyCollectionChanged = boundList as INotifyCollectionChanged; 
     if (notifyCollectionChanged != null) 
     { 
     notifyCollectionChanged.CollectionChanged += OnCollectionChanged; 
     } 
    } 

    void UpdateListBoxSelection() 
    { 
     // Temporarily detach from ListBox.SelectionChanged event 
     _listBox.SelectionChanged -= OnSelectionChanged; 

     // Synchronize selected ListBox items with bound list 
     _listBox.SelectedItems.Clear(); 
     foreach (var item in _boundList) 
     { 
     // References in _boundList might not be the same as in _listBox.Items 
     var i = _listBox.Items.IndexOf(item); 
     if (i >= 0) 
     { 
      _listBox.SelectedItems.Add(_listBox.Items[i]); 
     } 
     } 

     // Re-attach to ListBox.SelectionChanged event 
     _listBox.SelectionChanged += OnSelectionChanged; 
    } 

    void OnLoaded(object sender, RoutedEventArgs e) 
    { 
     // Init ListBox selection 
     UpdateListBoxSelection(); 
    } 

    void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     // Update ListBox selection 
     UpdateListBoxSelection(); 
    } 

    void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     // Update ListBox selection 
     UpdateListBoxSelection(); 
    } 

    void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
    { 
     // Temporarily deattach from INotifyCollectionChanged.CollectionChanged event. 
     var notifyCollectionChanged = _boundList as INotifyCollectionChanged; 
     if (notifyCollectionChanged != null) 
     { 
     notifyCollectionChanged.CollectionChanged -= OnCollectionChanged; 
     } 

     // Synchronize bound list with selected ListBox items 
     _boundList.Clear(); 
     foreach (var item in _listBox.SelectedItems) 
     { 
     _boundList.Add(item); 
     } 

     // Re-attach to INotifyCollectionChanged.CollectionChanged event. 
     if (notifyCollectionChanged != null) 
     { 
     notifyCollectionChanged.CollectionChanged += OnCollectionChanged; 
     } 
    } 
    } 
+0

Nie mogę sprawić, żeby to działało: wybieram jedną pozycję w polu listy, a następnie wykonuję polecenie, aby przenieść pozycję o jedną pozycję na liście. Odbywa się to poprzez zamianę go na następny element w kolekcji związany z ItemsSource. Następnie wybieram następny element, korzystając z przesłanego przez ciebie kodu. Końcowym wynikiem jest to, że zarówno pierwszy, jak i drugi element są zaznaczone w polu listy, nawet jeśli wstawiając punkt przerwania w UpdateListBoxSelection(), _listBox.SelectedItems zawiera tylko jeden element. – stijn

+0

ok to: najpierw musiałem wyczyścić SelectedItems, następnie zmodyfikować ItemsSource, a następnie ponownie wybrać. Z jakiegoś powodu pierwsza modyfikacja, a następnie zmiana zaznaczenia, nie powiodą się prawidłowo. – stijn

+0

Dla tych, którzy nadal nie mogliby sprawić, żeby to działało, upewnij się, że nie modyfikujesz kolorów motywu Windows tak jak ja. Okazuje się, że mój kolor tła ListBox pasował do koloru selekcji, gdy ListBox był nieostry, dzięki czemu wyglądało na to, że nic nie zostało wybrane. Zmień pędzel tła ListBox na czerwony, aby sprawdzić, czy tak właśnie się dzieje. Sprawiło, że spędziłem 2 godziny, aż zdałem sobie sprawę ... – Kyopaxa

0

Im przy użyciu obiektu EventToCommand na zaznaczeniu zmienione zdarzenie w XAML i przekazywanie tam ListBox jako parametr. Polecenie w MMVM zarządza ObservableCollection wybranych elementów. Łatwo i szybko;)

0

Rozwiązanie Briana Genisio i Samuela Jacka here są świetne. Zaimplementowałem to z powodzeniem. Ale miałem też przypadek, w którym to nie zadziałało, a ponieważ nie jestem ekspertem od WPF lub .Net, nie udało mi się go debugować. Nadal nie jestem pewien, co to była kwestia, ale we właściwym czasie znalazłem obejście dla wiązania wielokrotnego wyboru. I w tym rozwiązaniu nie musiałem przechodzić do DataContext.

To rozwiązanie jest przeznaczone dla osób, które nie mogą wykonać powyższych 2 rozwiązań. Sądzę, że to rozwiązanie nie zostanie uznane za MVVM. Tak to wygląda. Załóżmy, że masz 2 kolekcje w ViewModel:

public ObservableCollection<string> AllItems { get; private set; } 
public ObservableCollection<string> SelectedItems { get; private set; } 

Potrzebny jest pole listy:

<ListBox x:Name="MyListBox" ItemsSource="{Binding AllItems}" SelectionMode="Multiple" /> 

teraz dodać kolejny ListBox i powiązać go z selectedItems i ustaw Widoczność:

<ListBox x:Name="MySelectedItemsListBox" ItemsSource="{Binding SelectedItems, Mode=OneWayToSource}" SelectionMode="Multiple" Visibility="Collapsed" /> 

Teraz w kodzie za stroną WPF, dodaj do konstruktora po metoda InitializeComponent():

MyListBox.SelectionChanged += MyListBox_SelectionChanged; 

i dodać metodę:

private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 
{ 
    MySelectedItemsListBox.ItemsSource = MyListBox.SelectedItems; 
} 

i gotowe. To zadziała na pewno.Myślę, że to może być użyte w Silverlight również, jeśli powyższe rozwiązanie nie działa.

0

Dla tych, którzy nadal nie mogli wykonać candritzky'ego odpowiedzi na pracę, upewnij się, że nie zmodyfikowałeś kolorów motywu Windows tak jak ja. Okazuje się, że mój kolor tła ListBox pasował do koloru selekcji, gdy ListBox był nieostry, dzięki czemu wyglądało na to, że nic nie zostało wybrane.

Zmień pędzel tła ListBox na czerwony, aby sprawdzić, czy to właśnie się dzieje. To zmusiło mnie do spędzenia 2 godzin, aż zrozumiałem ...

Powiązane problemy