Sincronizar SelectedItems em uma checkbox de listview muliselect com uma coleção no ViewModel

Eu tenho uma checkbox de listview de seleção múltipla em um aplicativo SL3 usando prisma e preciso de uma coleção no meu viewmodel que contenha os itens atualmente selecionados na checkbox de listview.

O viewmodel não sabe nada sobre a exibição, portanto, não tem access ao controle de checkbox de listview. Também preciso poder limpar os itens selecionados na checkbox de listview do viewmodel.

Não tenho certeza de como abordar esse problema

obrigado Michael

Então, suponha que você tenha um ViewModel com as seguintes propriedades:

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

Você começaria vinculando sua coleção AllItems ao ListBox:

  

O problema é que a propriedade SelectedItems no ListBox não é um DependencyProperty. Isso é muito ruim, já que você não pode vinculá-lo a algo em seu ViewModel.

A primeira abordagem é apenas colocar essa lógica no code-behind, para ajustar o 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); } } 

Essa abordagem funcionará, mas é realmente feia. Minha abordagem preferida é extrair esse comportamento em um “comportamento anexado”. Se você fizer isso, poderá eliminar completamente o code-behind e configurá-lo no XAML. O bônus é que este “Attached Behavior” é agora reutilizável em qualquer ListBox:

  

E aqui está o código para o comportamento anexado:

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

Eu queria ter verdadeira binding bidirecional para que a seleção ListBox reflita os itens contidos na coleção SelectedItems do ViewModel subjacente. Isso me permite controlar a seleção por lógica na camada ViewModel.

Aqui estão minhas modificações para a class SelectedItemsBehavior. Eles sincronizam a coleção ListBox.SelectedItems com a propriedade ViewModel subjacente se a propriedade ViewModel implementa INotifyCollectionChanged (por exemplo, implementado pelo 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; } } } 

Obrigado por isso! Eu adicionei uma pequena atualização para suportar o carregamento inicial e alteração de DataContext.

Felicidades,

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

Comportamento existente atualizado com a seleção dos itens na coleção alterada e desativada

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

A solução original acima funciona se você lembrar de criar uma instância da coleção observável primeiro! Além disso, você precisa garantir que o tipo de conteúdo da coleção Observable corresponda ao tipo de conteúdo da sua Origem de Item da Caixa de Dados (se estiver se desviando do exemplo exato mencionado acima).

Aqui está um blog com uma solução para esse problema, incluindo um aplicativo de exemplo para que você possa ver exatamente como fazê-lo funcionar: http://alexshed.spaces.live.com/blog/cns!71C72270309CE838!149.entry

Acabei de implementar isso na minha aplicação e resolve bem o problema

A solução para mim foi combinar a atualização de Alessandro Pilotti com o comportamento do Brian Genisio. Mas remova o código para o DataContext que altera o Silverlight 4 e não suporta isso.

Se você estiver ligando a checkbox de listview a um ObservableCollection o acima funciona bem, mas se você estiver vinculando a objects complexos, como ObservableCollection SelectedItems { get; private set; } ObservableCollection SelectedItems { get; private set; } ObservableCollection SelectedItems { get; private set; } através de um DataTemplate parece não funcionar. Isso devido à implementação padrão do método Equals que a coleção está usando. Você pode resolver isso dizendo ao seu object Person quais campos comparar quando determinar se os objects são iguais, isso é feito implementando a interface IEquatable em seu object.

Depois disso, o código IndexOf (item) funcionará e será capaz de comparar se os objects forem iguais e selecionar o item na 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]); 

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

Estou usando o object EventToCommand na seleção de evento alterado em XAML e passando lá ListBox como um parâmetro. Que comando no MMVM está gerenciando ObservableCollection de itens selecionados. É fácil e rápido;)

A solução de Brian Genisio e Samuel Jack é ótima. Eu o implementei com sucesso. Mas também tive um caso em que isso não funcionou e, como não sou especialista em WPF ou .Net, não consegui depurá-lo. Ainda não estou certo sobre qual era o problema, mas, no devido tempo, encontrei uma solução alternativa para a vinculação multiselect. E nessa solução, não precisei acessar o DataContext.

Essa solução é para pessoas que não conseguiram fazer com que as duas soluções acima funcionassem. Eu acho que esta solução não seria considerada como MVVM. É assim. Suponha que você tenha 2 collections no ViewModel:

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

Você precisa de uma checkbox de listview:

  

Agora adicione outro ListBox e vincule-o a SelectedItems e defina Visibility:

  

Agora, no código por trás da página do WPF, adicione ao construtor após o método InitializeComponent ():

 MyListBox.SelectionChanged += MyListBox_SelectionChanged; 

E adicione um método:

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

E você está feito. Isso funcionará com certeza. Eu acho que isso pode ser usado no Silverlight também se a solução acima não funcionar.

Para aqueles que ainda não conseguiram fazer a resposta do candritzky funcionar, certifique-se de não modificar as colors do tema do Windows como eu. Acontece que minha cor de fundo da ListBox estava combinando com a cor da seleção quando o ListBox estava fora de foco, fazendo com que parecesse que nada estava selecionado.

Mude o pincel de fundo do ListBox para vermelho para verificar se é isso que está acontecendo com você. Isso me fez gastar 2 horas até eu perceber …