Sync Multi Select Listbox with ViewModel

Today one of my colleagues at Infusion asked me how he could sync up the selection of a ListBox with his ViewModel. WPF supports already single mode selection via the ICollectionView yet when it comes to MultiSelect there is no out of the box support in WPF.

Attached Properties to the rescue ….. :)

I quickly when in my VS and wrote up a simple Attached property that hooks to the SelectionChanged event of the ListBox and populates a list of selected items to the ViewModel.

This is how the attached property looks like

   1: #region SelectedItems

   2:  

   3: /// <summary>

   4: /// SelectedItems Attached Dependency Property

   5: /// </summary>

   6: public static readonly DependencyProperty SelectedItemsProperty =

   7:     DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(ListBoxHelper),

   8:         new FrameworkPropertyMetadata((IList)null,

   9:             new PropertyChangedCallback(OnSelectedItemsChanged)));

  10:  

  11: /// <summary>

  12: /// Gets the SelectedItems property.  This dependency property 

  13: /// indicates ....

  14: /// </summary>

  15: public static IList GetSelectedItems(DependencyObject d)

  16: {

  17:     return (IList)d.GetValue(SelectedItemsProperty);

  18: }

  19:  

  20: /// <summary>

  21: /// Sets the SelectedItems property.  This dependency property 

  22: /// indicates ....

  23: /// </summary>

  24: public static void SetSelectedItems(DependencyObject d, IList value)

  25: {

  26:     d.SetValue(SelectedItemsProperty, value);

  27: }

  28:  

  29: /// <summary>

  30: /// Handles changes to the SelectedItems property.

  31: /// </summary>

  32: private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

  33: {

  34:     var listBox = (ListBox)d;

  35:     ReSetSelectedItems(listBox);

  36:     listBox.SelectionChanged += delegate

  37:     {

  38:         ReSetSelectedItems(listBox);

  39:     };

  40: }

  41:  

  42: #endregion

  43:  

  44: private static void ReSetSelectedItems(ListBox listBox)

  45: {

  46:     IList selectedItems = GetSelectedItems(listBox);

  47:     selectedItems.Clear();

  48:     if (listBox.SelectedItems != null)

  49:     {

  50:         foreach (var item in listBox.SelectedItems)

  51:             selectedItems.Add(item);

  52:     }

  53: }

and here is how you would use it in the XAML

   1: <ListBox ItemsSource="{Binding MyData}" Grid.Column="1" local:ListBoxHelper.SelectedItems="{Binding SelectedData}"

   2:                  SelectionMode="Extended"/>

As you can see all you have to set is the List you want to populate in the ViewModel and the rest is taken care of by the Attached Property.

Please Note: This code was implemented quickly without any testing to prove an implementation idea, so please do review it before putting it in production :)

Download the source code

About these ads

14 thoughts on “Sync Multi Select Listbox with ViewModel

  1. Pingback: Dew Drop - June 2, 2009 | Alvin Ashcraft's Morning Dew

  2. Dear Sir,

    I hope you are doing well. I got this email address from one of your contribution web site. I have launched a web site http://www.codegain.com and it is basically aimed C#,JAVA,VB.NET,ASP.NET,AJAX,Sql Server,Oracle,WPF,WCF and etc resources, programming help, articles, code snippet, video demonstrations and problems solving support. I would like to invite you as an author and a supporter.
    Looking forward to hearing from you and hope you will join with us soon.

    Thank you
    RRaveen
    Founder CodeGain.com

  3. Here’s a version that updates the selection when the SelectedItems list changes… so you can programatically change the multi-selection (The bound SelectedItems must be an ObservableCollection.

    - Brian

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections;
    using System.Windows;
    using System.Windows.Controls;
    using System.Collections.ObjectModel;

    namespace UndoRedoSample
    {
    public class ListBoxHelper
    {
    #region SelectedItems

    ///
    /// SelectedItems Attached Dependency Property
    ///
    public static readonly DependencyProperty SelectedItemsProperty =
    DependencyProperty.RegisterAttached(“SelectedItems”, typeof(ObservableCollection), typeof(ListBoxHelper),
    new FrameworkPropertyMetadata((ObservableCollection)null,
    new PropertyChangedCallback(OnSelectedItemsChanged)));

    ///
    /// Gets the SelectedItems property. This dependency property
    /// indicates ….
    ///
    public static ObservableCollection GetSelectedItems(DependencyObject d)
    {
    return (ObservableCollection)d.GetValue(SelectedItemsProperty);
    }

    ///
    /// Sets the SelectedItems property. This dependency property
    /// indicates ….
    ///
    public static void SetSelectedItems(DependencyObject d, ObservableCollection value)
    {
    d.SetValue(SelectedItemsProperty, value);
    }

    ///
    /// Called when SelectedItems is set
    ///
    private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    var listBox = (ListBox)d;
    ListBoxSelectionChanged(listBox);
    ObservableCollection selectedItems = GetSelectedItems(listBox);
    selectedItems.CollectionChanged += delegate
    {
    BoundCollectionChanged(listBox);
    };
    listBox.SelectionChanged += delegate
    {
    ListBoxSelectionChanged(listBox);
    };
    }

    private static bool _busy = false;

    ///
    /// Update the listbox
    ///
    static void BoundCollectionChanged(ListBox listBox)
    {
    if (_busy) // whichever one called first, blocks the other
    return;

    _busy = true;
    ObservableCollection selectedItems = GetSelectedItems(listBox);
    listBox.SelectedItems.Clear();
    if (selectedItems != null)
    {
    foreach (var item in selectedItems)
    listBox.SelectedItems.Add(item);
    }
    _busy = false;
    }

    #endregion

    ///
    /// Update the bound collection
    ///
    private static void ListBoxSelectionChanged(ListBox listBox)
    {
    if (_busy) // whichever one called first, blocks the other
    return;

    _busy = true;
    ObservableCollection selectedItems = GetSelectedItems(listBox);
    selectedItems.Clear();
    if (listBox.SelectedItems != null)
    {
    foreach (var item in listBox.SelectedItems)
    selectedItems.Add(item);
    }
    _busy = false;
    }
    }
    }

  4. IList must be used instead Observable collection (as it is in original sample code). And to add delegate to CollectionChanged, collection of items must be cast to INotifyCollectionChanged:
    ((INotifyCollectionChanged)selectedItems).CollectionChanged += delegate
    {
    BoundCollectionChanged(listBox);
    };

  5. Hi Marlon

    While this solution may work for WPF, I can’t get it to work for Silverlight MVVM. Could you provide us with a Silverlight solution for this problem?

    Thanks

    • @Marlon: Great Article, saved me a lot of time
      @Brian: I will try your code, as I also missed the possibility to select items programatically
      @Bavo: I’m developing for Windows Phone 7 (even more restricted Silverlight) and you just have to replace FrameworkPropertyMetadata by PropertyMetadata in ListBoxHelper. In my case Visual Studio didn’t recognize the ListBoxHelper in Xaml and showed an error, but after compiling once, it disappeared and works fine.

  6. Hi,
    This code works great, I have one thing I will like to add to it and want to know if you already did that: I want to use SelectValuePath property to bind the items to the specific value path and not just to the whole item.

    The reason is that many time I use some wrapper around the real object and in my ViewModel I want collection of the real items, not the wrappers.

    I’ve look at the code of Selector class but most (if now all) of the methods it use for setting the SelectedValue are internal and thus not accessible to me.

    Thank you,
    Ido.

  7. Pingback: WPF Nested ListBox Databinding using ViewModel

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s