Articles in categories

Articles

Get multiple SelectedItems from ListBox with multiple select enabled (AttachedProperty)

You cannot, out of the box, get the SelectedItems from a ListView which has Multiple selection enabled in MVVM. This is because the SelectedItems property isn't a Dependency Property but a ReadOnly property.

I found a working solution that defines an attached property that you attach to the ListBox and allows you to specify a collection that you want to be kept in sync with the SelectedItems collection of the ListBox.

In order to work properly, the collection properrty you use should be of type INotifyCollectionChanged or ObservableCollection.

The AttachedProperty class:

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

    public static readonly DependencyProperty ItemsProperty = 
      DependencyProperty.RegisterAttached( "Items", typeof (IList), typeof (ListViewSelectedItems),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 && e.NewValue != null)
      {
        SelectedItemsBehavior itemsBehavior = GetOrCreateBehavior(target, e.NewValue as IList);
        itemsBehavior.RemoveSelectionChangedHandler();
        target.SelectedItems.Clear();
        foreach (var item in e.NewValue as IList)
        {
          target.SelectedItems.Add(item);
        }
        itemsBehavior.AddSelectionChangedHandler();
      }
    }

    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);
      }
      else
      {
        behavior.SetNewList(list);
      }

      return behavior;
    }
  }

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

    public SelectedItemsBehavior(ListBox listBox, IList boundList)
    {
      _boundList = boundList;
      _listBox = listBox;
      _listBox.Loaded += new RoutedEventHandler(ListBoxLoaded);
      AddCollectionChangedHandler();
    }

    private void SelectedItemsBehaviorCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
      RemoveSelectionChangedHandler();
      _listBox.SelectedItems.Clear();
      foreach (var item in _boundList)
      {
        _listBox.SelectedItems.Add(item);
      }
      AddSelectionChangedHandler();
    }

    private void ListBoxLoaded(object sender, RoutedEventArgs e)
    {
      foreach (var item in _boundList)
      {
        _listBox.SelectedItems.Add(item);
      }
    }

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

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

    public void SetNewList(IList boundList)
    {
      RemoveCollectionChangedHandler();
      _boundList = boundList;
      AddCollectionChangedHandler();
    }

    public void AddSelectionChangedHandler()
    {
      _listBox.SelectionChanged += OnSelectionChanged;
    }

    public void RemoveSelectionChangedHandler()
    {
      _listBox.SelectionChanged -= OnSelectionChanged;
    }

    public void AddCollectionChangedHandler()
    {
      if (_boundList == null) return;
      (_boundList as INotifyCollectionChanged).CollectionChanged +=
        new NotifyCollectionChangedEventHandler(SelectedItemsBehaviorCollectionChanged);
    }

    public void RemoveCollectionChangedHandler()
    {
      if (_boundList == null) return;
      (_boundList as INotifyCollectionChanged).CollectionChanged -=
        new NotifyCollectionChangedEventHandler(SelectedItemsBehaviorCollectionChanged);
    }
  }

And then in you xaml:

    <ListBox x:Name="PhotoCollection"
             Grid.Row="2"
             Grid.ColumnSpan="4"
             ItemsSource="{Binding PhotoCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             ScrollViewer.HorizontalScrollBarVisibility="Disabled"
             SelectionMode="Extended"
             UserControls:ListViewSelectedItems.Items="{Binding SelectedPhotos, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             Margin="3">

And my ViewModel's SelectedItems property (SelectedPhotos)

    public static readonly DependencyProperty SelectedPhotosProperty = DependencyProperty.Register("SelectedPhotos", typeof(ObservableCollection<string>), typeof(ShowPhotosViewModel), new UIPropertyMetadata(new ObservableCollection<string>()));
    public ObservableCollection<string> SelectedPhotos
    {
      get { return (ObservableCollection<string>)GetValue(SelectedPhotosProperty); }
      set { SetValue(SelectedPhotosProperty, value); }
    }

That does it.