.net wpf mvvm multi-select virtualizingstackpanel

.net - Sincronizando multiselección de ListBox con MVVM



wpf multi-select (1)

Tengo dos vistas de algunos datos: una vista de lista (un ListBox ahora, pero he querido cambiar a ListView ) y una representación gráfica elegante en un mapa. En cualquiera de las vistas, el usuario puede hacer clic en un objeto y se seleccionará en ambas vistas. La selección múltiple también es posible, por lo que cada instancia de ViewModel tiene su propia propiedad IsSelected .

Actualmente estoy vinculando ListBoxItem.IsSelected to ViewModel.IsSelected , pero esto solo funciona correctamente si el ListBox NO está virtualizando ( vea aquí ). Desafortunadamente, deshabilitar la virtualización perjudica el rendimiento y mi aplicación se ha vuelto demasiado lenta.

Así que tengo que habilitar la virtualización de nuevo. Para mantener la propiedad ViewModel.IsSelected de los elementos fuera de pantalla, noté que ListBox y ListView tienen un evento SelectionChanged que puedo usar (probablemente) para propagar el estado de selección de ListBox/ListView a ViewModel .

Mi pregunta es, ¿cómo propagar el estado de selección en la dirección inversa? ¡La propiedad SelectedItems de ListBox/ListView es de solo lectura! Supongamos que el usuario hace clic en un elemento de la representación gráfica, pero está fuera de pantalla en la lista. Si acabo de configurar ViewModel.IsSelected , ListBox/ListView conocerá la nueva selección y, como consecuencia, no podrá anular la selección de ese elemento si el usuario hace clic en un elemento diferente de la lista. Podría llamar a ListBox.ScrollIntoView desde ViewModel , pero hay un par de problemas:

  • En mi interfaz de usuario, en realidad es posible seleccionar dos elementos con un solo clic si están en la misma ubicación gráficamente, aunque pueden ubicarse en ubicaciones completamente diferentes en ListBox/ListView .
  • Rompe el aislamiento de ViewModel (mi ViewModel no tiene conocimiento de WPF y me gustaría que siga siendo así).

Entonces, mis queridos expertos de WPF, ¿alguna idea?

EDITAR: Terminé cambiando a un control de Infragística y usando una solución fea y bastante lenta. El punto es que ya no necesito una respuesta.


Puedes crear un Behavior que sincronice ListBox.SelectedItems con una colección en tu ViewModel:

public class MultiSelectionBehavior : Behavior<ListBox> { protected override void OnAttached() { base.OnAttached(); if (SelectedItems != null) { AssociatedObject.SelectedItems.Clear(); foreach (var item in SelectedItems) { AssociatedObject.SelectedItems.Add(item); } } } public IList SelectedItems { get { return (IList)GetValue(SelectedItemsProperty); } set { SetValue(SelectedItemsProperty, value); } } public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged)); private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var behavior = o as MultiSelectionBehavior; if (behavior == null) return; var oldValue = e.OldValue as INotifyCollectionChanged; var newValue = e.NewValue as INotifyCollectionChanged; if (oldValue != null) { oldValue.CollectionChanged -= behavior.SourceCollectionChanged; behavior.AssociatedObject.SelectionChanged -= behavior.ListBoxSelectionChanged; } if (newValue != null) { behavior.AssociatedObject.SelectedItems.Clear(); foreach (var item in (IEnumerable)newValue) { behavior.AssociatedObject.SelectedItems.Add(item); } behavior.AssociatedObject.SelectionChanged += behavior.ListBoxSelectionChanged; newValue.CollectionChanged += behavior.SourceCollectionChanged; } } private bool _isUpdatingTarget; private bool _isUpdatingSource; void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (_isUpdatingSource) return; try { _isUpdatingTarget = true; if (e.OldItems != null) { foreach (var item in e.OldItems) { AssociatedObject.SelectedItems.Remove(item); } } if (e.NewItems != null) { foreach (var item in e.NewItems) { AssociatedObject.SelectedItems.Add(item); } } if (e.Action == NotifyCollectionChangedAction.Reset) { AssociatedObject.SelectedItems.Clear(); } } finally { _isUpdatingTarget = false; } } private void ListBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { if (_isUpdatingTarget) return; var selectedItems = this.SelectedItems; if (selectedItems == null) return; try { _isUpdatingSource = true; foreach (var item in e.RemovedItems) { selectedItems.Remove(item); } foreach (var item in e.AddedItems) { selectedItems.Add(item); } } finally { _isUpdatingSource = false; } } }

Este comportamiento se puede utilizar como se muestra a continuación:

<ListBox ItemsSource="{Binding Items}" DisplayMemberPath="Name" SelectionMode="Extended"> <i:Interaction.Behaviors> <local:MultiSelectionBehavior SelectedItems="{Binding SelectedItems}" /> </i:Interaction.Behaviors> </ListBox>

(tenga en cuenta que la colección SelectedItems en su ViewModel debe inicializarse; el comportamiento no lo establecerá, solo cambiará su contenido)