Sincronizar elementos seleccionados en un cuadro de lista de muliselección con una colección en ViewModel

Tengo un cuadro de lista de selección múltiple en una aplicación SL3 que usa prisma y necesito una colección en mi modelo de vista que contiene los elementos seleccionados actualmente en el cuadro de lista.

El modelo de vista no sabe nada sobre la vista, por lo que no tiene acceso al control de cuadro de lista. También necesito poder borrar los elementos seleccionados en el cuadro de lista del modelo de vista.

No estoy seguro de cómo abordar este problema

gracias Michael

Por lo tanto, supongamos que tiene un ViewModel con las siguientes propiedades:

public ObservableCollection AllItems { get; private set; } public ObservableCollection SelectedItems { get; private set; } 

Comenzará por vincular su colección AllItems al ListBox:

  

El problema es que la propiedad SelectedItems en ListBox no es DependencyProperty. Esto es bastante malo, ya que no puede vincularlo a algo en su ViewModel.

El primer enfoque es simplemente poner esta lógica en el código subyacente, para ajustar el 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); } } 

Este enfoque funcionará, pero es realmente feo. Mi enfoque preferido es extraer este comportamiento en un “Comportamiento adjunto”. Si lo hace, puede eliminar por completo su código subyacente y configurarlo en el XAML. La ventaja es que este “Comportamiento adjunto” ahora se puede reutilizar en cualquier ListBox:

  

Y aquí está el código para el comportamiento adjunto:

 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); } } } 

Quería tener un enlace bidireccional verdadero para que la selección de ListBox refleje los elementos contenidos en la colección SelectedItems del ViewModel subyacente. Esto me permite controlar la selección por lógica en la capa ViewModel.

Aquí están mis modificaciones a la clase SelectedItemsBehavior. Sincronizan la colección ListBox.SelectedItems con la propiedad ViewModel subyacente si la propiedad ViewModel implementa INotifyCollectionChanged (por ejemplo, implementado por el tipo ObservableCollection ).

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

¡Gracias por esto! Agregué una pequeña actualización para soportar la carga inicial y el cambio del DataContext.

Aclamaciones,

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); } } } 

Comportamiento existente actualizado con Seleccionar los elementos en la Colección modificada y reenviada

http://rnragu.blogspot.com/2011/04/multiselect-listbox-in-silverlight-use.html

La solución original anterior funciona si recuerda crear primero una instancia de la colección observable. Además, debe asegurarse de que el tipo de contenido de la colección Observable coincida con el tipo de contenido de su ListBox ItemSource (si se está desviando del ejemplo exacto mencionado anteriormente).

Aquí hay un blog con una solución para este problema, que incluye una aplicación de ejemplo para que pueda ver exactamente cómo hacer que funcione: http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry

Acabo de implementar esto en mi aplicación y resuelve el problema muy bien

La solución para mí fue combinar la actualización de Alessandro Pilotti con el comportamiento adjunto de Brian Genisio. Pero elimine el código del DataContext cambiando Silverlight 4 no es compatible.

Si está vinculando el listbox a un ObservableCollection lo anterior funciona bien, pero si está vinculando objetos complejos como ObservableCollection SelectedItems { get; private set; } ObservableCollection SelectedItems { get; private set; } ObservableCollection SelectedItems { get; private set; } través de una DataTemplate no parece funcionar. Esto debido a la implementación predeterminada del método Equals que está usando la colección. Puede resolver esto diciendo a su objeto Person qué campos comparar para determinar si los objetos son iguales, esto se hace mediante la implementación de la interfaz IEquatable en su objeto.

Después de eso, el código IndexOf (artículo) funcionará y podrá comparar si los objetos son iguales y seleccionar el elemento de la lista.

 // 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]); 

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

Estoy usando el objeto EventToCommand en la selección del evento cambiado en XAML y pasando ListBox como parámetro. Than command en MMVM está gestionando ObservableCollection de elementos seleccionados. Es fácil y rápido;)

La solución de Brian Genisio y Samuel Jack aquí es genial. Lo he implementado con éxito. Pero también tuve un caso donde esto no funcionó y dado que no soy un experto con WPF o .Net, no pude depurarlo. Todavía no estoy seguro de cuál fue el problema, pero a su debido tiempo, encontré una solución para el enlace multiselect. Y en esta solución, no tuve que llegar al DataContext.

Esta solución es para personas que no pueden hacer que las soluciones anteriores 2 funcionen. Supongo que esta solución no se consideraría MVVM. Dice así. Supongamos que tiene 2 colecciones en ViewModel:

 public ObservableCollection AllItems { get; private set; } public ObservableCollection SelectedItems { get; private set; } 

Necesitas un cuadro de lista:

  

Ahora agregue otro ListBox y vincúlelo a SelectedItems y establezca Visibility:

  

Ahora, en el código detrás de la página WPF, agregue al constructor después del método InitializeComponent ():

 MyListBox.SelectionChanged += MyListBox_SelectionChanged; 

Y agrega un método:

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

Y has terminado. Esto funcionará con seguridad. Supongo que esto también se puede usar en Silverlight si la solución anterior no funciona.

Para aquellos que todavía no pueden hacer que candritzky responda al trabajo, asegúrese de no modificar los colores de tema de Windows como yo. Resultó que mi color de fondo ListBox coincidía con el color de selección cuando el ListBox estaba desenfocado, por lo que parece que no se seleccionó nada.

Cambia el pincel de fondo de ListBox a rojo para comprobar si esto es lo que te está sucediendo. Me hizo pasar 2 horas hasta que me di cuenta …

Intereting Posts