WPF M-V-VM: получить выбранные элементы из ListCollectionView?
У меня есть приложение WPF, использующее шаблон Model-View-ViewModel.
В моем ViewModel у меня есть ListCollectionView для хранения списка элементов.
Этот ListCollectionView привязан к списку в моем представлении.
<ListBox Grid.Row="1" ItemsSource="{Binding Useragents}" SelectionMode="Multiple"/>
В списке есть SelectionMode=Multiple, так что вы можете выбрать несколько элементов одновременно. Теперь ViewModel должен знать, какие элементы были выбраны.
Проблема в том, что в шаблоне View-Model-ViewModel ViewModel не имеет доступа к представлению, поэтому я не могу просто спросить список элементов, которые были выбраны. Все, что у меня есть-это ListCollectionView, но я не могу найти способ найти, какие элементы были выбраны там.
Итак, как мне найти, какие элементы были выбраны в списке? Или трюк, чтобы достичь этого (может быть, привязать что-то к логическому "IsSelected" в моих элементах? Но что именно? Но как?)
Может быть, кто-то, кто использует этот шаблон, тоже может помочь мне здесь?
9 ответов:
Эталонная реализация PRISM MVVM имеет поведение, называемое SynchronizeSelectedItems, используемое в Prism4\MVVM RI\MVVM.Клиент\Представления \ MultipleSelectionView.xaml, который синхронизирует проверяемые элементы со свойством ViewModel с именем
Selections:<ListBox Grid.Column="0" Grid.Row="1" IsTabStop="False" SelectionMode="Multiple" ItemsSource="{Binding Question.Range}" Margin="5"> <ListBox.ItemContainerStyle> <!-- Custom style to show the multi-selection list box as a collection of check boxes --> <Style TargetType="ListBoxItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ListBoxItem"> <Grid Background="Transparent"> <CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" IsHitTestVisible="False" IsTabStop="True" AutomationProperties.AutomationId="CheckBoxAutomationId"> <ContentPresenter/> </CheckBox> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ListBox.ItemContainerStyle> <i:Interaction.Behaviors> <!-- Custom behavior that synchronizes the selected items with the view models collection --> <Behaviors:SynchronizeSelectedItems Selections="{Binding Selections}"/> </i:Interaction.Behaviors> </ListBox>Перейти к http://compositewpf.codeplex.com/ и захватить все это или использовать это:
//=================================================================================== // Microsoft patterns & practices // Composite Application Guidance for Windows Presentation Foundation and Silverlight //=================================================================================== // Copyright (c) Microsoft Corporation. All rights reserved. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT // LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE. //=================================================================================== // The example companies, organizations, products, domain names, // e-mail addresses, logos, people, places, and events depicted // herein are fictitious. No association with any real company, // organization, product, domain name, email address, logo, person, // places, or events is intended or should be inferred. //=================================================================================== using System; using System.Collections; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Windows; using System.Windows.Controls; using System.Windows.Interactivity; namespace MVVM.Client.Infrastructure.Behaviors { /// <summary> /// Custom behavior that synchronizes the list in <see cref="ListBox.SelectedItems"/> with a collection. /// </summary> /// <remarks> /// This behavior uses a weak event handler to listen for changes on the synchronized collection. /// </remarks> public class SynchronizeSelectedItems : Behavior<ListBox> { public static readonly DependencyProperty SelectionsProperty = DependencyProperty.Register( "Selections", typeof(IList), typeof(SynchronizeSelectedItems), new PropertyMetadata(null, OnSelectionsPropertyChanged)); private bool updating; private WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs> currentWeakHandler; [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Dependency property")] public IList Selections { get { return (IList)this.GetValue(SelectionsProperty); } set { this.SetValue(SelectionsProperty, value); } } protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged; this.UpdateSelectedItems(); } protected override void OnDetaching() { this.AssociatedObject.SelectionChanged += this.OnSelectedItemsChanged; base.OnDetaching(); } private static void OnSelectionsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var behavior = d as SynchronizeSelectedItems; if (behavior != null) { if (behavior.currentWeakHandler != null) { behavior.currentWeakHandler.Detach(); behavior.currentWeakHandler = null; } if (e.NewValue != null) { var notifyCollectionChanged = e.NewValue as INotifyCollectionChanged; if (notifyCollectionChanged != null) { behavior.currentWeakHandler = new WeakEventHandler<SynchronizeSelectedItems, object, NotifyCollectionChangedEventArgs>( behavior, (instance, sender, args) => instance.OnSelectionsCollectionChanged(sender, args), (listener) => notifyCollectionChanged.CollectionChanged -= listener.OnEvent); notifyCollectionChanged.CollectionChanged += behavior.currentWeakHandler.OnEvent; } behavior.UpdateSelectedItems(); } } } private void OnSelectedItemsChanged(object sender, SelectionChangedEventArgs e) { this.UpdateSelections(e); } private void UpdateSelections(SelectionChangedEventArgs e) { this.ExecuteIfNotUpdating( () => { if (this.Selections != null) { foreach (var item in e.AddedItems) { this.Selections.Add(item); } foreach (var item in e.RemovedItems) { this.Selections.Remove(item); } } }); } private void OnSelectionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { this.UpdateSelectedItems(); } private void UpdateSelectedItems() { this.ExecuteIfNotUpdating( () => { if (this.AssociatedObject != null) { this.AssociatedObject.SelectedItems.Clear(); foreach (var item in this.Selections ?? new object[0]) { this.AssociatedObject.SelectedItems.Add(item); } } }); } private void ExecuteIfNotUpdating(Action execute) { if (!this.updating) { try { this.updating = true; execute(); } finally { this.updating = false; } } } } }
Необходимо создать ViewModel, который имеет понятие IsSelected на нем и привязан к свойству IsSelected фактического ListBoxItem, который представляет его в представлении, используя стандартную архитектуру Привязок WPF.
Тогда в вашем коде, который знает о Вашей ViewModel, но не о том, что она представлена каким-либо конкретным представлением, можно просто использовать это свойство, чтобы узнать, какие элементы из модели на самом деле выбраны независимо от выбора конструкторов для того, как она представлена в вид.
Посмотрите на этот блог Джоша Смита первоначально выбранный элемент при привязке к Сгруппированному ICollectionView
Решение Дрю Марша работает очень хорошо, я рекомендую его. И у меня есть другое решение !
Представление модели ViewModel-это пассивное представление, Вы также можете использовать модель представления для доступа к некоторым данным вашей презентации без связи с WPF (этот паттерн используется в примереStocktrader изPRISM ).
Ответ Дрю Марша хорош, если у вас есть небольшой список, если у вас есть большой список, то хит производительности для поиска всех выбранных вами элементов может быть неприятным! Мое любимое решение-создать вложенное свойство в вашем списке, которое затем привязывается к ObservableCollection, содержащему выбранные вами элементы. Затем с помощью вложенного свойства вы подписываетесь на событие items SelectionChanged для добавления / удаления элементов из вашей коллекции.
Для меня лучший ответ-это немного нарушить принцип MVVM.
О коде позади 1. Instanciate вашей ViewModel 2. добавить обработчик события SelectionChanged 3. выполните итерацию по выбранным элементам и добавьте каждый элемент в список viewModel
ViewModel viewModel = new ViewModel(); viewModel.SelectedModules = new ObservableCollection<string>(); foreach (var selectedModule in listBox1.SelectedItems) { viewModel.SelectedModules.Add(selectedModule.ToString()); }
Вот еще один вариант шаблона View-Model-ViewModel, где ViewModel имеет доступ к представлению через интерфейс IView.
Я встречал довольно много сценариев, в которых вы не можете использовать привязку WPF, а затем вам нужен способ в коде синхронизировать состояние между представлением и ViewModel.
Как это можно сделать показано здесь:
Решение Дэвида Роджерса великолепно и подробно описано в следующем соответствующем вопросе:
Свойство selecteditems синхронизации в muliselect ListBox с коллекции в модели представления
Comments