MVVM Multiselect Listbox

Although MVVM is a great pattern you have to learn to work with it somethings are hard to do until you get the hang of it.

One of these things is doing a Multiselect Listbox. My first idea Google it, of course. The best solution I found was this one by Marlon Grech. Marlon has a lot of good stuff about WPF in his blog and he definitely knows what he is talking about. This solution however became a little slow when I wanted to perform a Select All and Unselect All on the list.

I decided to implement a specialized list for this kind of situation. I call it a SelectionList and it is a list of SelectionItems. The idea is to have a collection of items that have a IsSelected property and a Item property that contains the real value you want. I think the code speaks for itself.

public class SelectionItem<T> : INotifyPropertyChanged
{
    #region Fields

        private bool isSelected;

        private T item;

        #endregion

    #region Properties

        public bool IsSelected
        {
            get { return isSelected; }
            set
            {
                if (value == isSelected) return;
                isSelected = value;
                OnPropertyChanged("IsSelected");
                OnSelectionChanged();
            }
        }

        public T Item
        {
            get { return item; }
            set
            {
                if (value.Equals(item)) return;
                item = value;
                OnPropertyChanged("Item");
            }
        }

        #endregion

    #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        public event EventHandler SelectionChanged;

        #endregion

    #region ctor

        public SelectionItem(T item)
            : this(false, item)
        {
        }

        public SelectionItem(bool selected, T item)
        {
            this.isSelected = selected;
            this.item = item;
        }

        #endregion

    #region Event invokers

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler changed = PropertyChanged;
            if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
        }

        private void OnSelectionChanged()
        {
            EventHandler changed = SelectionChanged;
            if (changed != null) changed(this, EventArgs.Empty);
        }

        #endregion
}

 The SelectionItem class is what really makes things happen. It takes an ordinary class like an string and wraps it adding a IsSelected property. This is the class of the objects that will be bound to each ListItem of the ListBox.

public class SelectionList<T> : 
    ObservableCollection<SelectionItem<T>> where T : IComparable<T>
{
    #region Properties

        /// <summary>
        /// Returns the selected items in the list
        /// </summary>
        public IEnumerable<T> SelectedItems
        {
            get { return this.Where(x => x.IsSelected).Select(x => x.Item); }
        }

        /// <summary>
        /// Returns all the items in the SelectionList
        /// </summary>
        public IEnumerable<T> AllItems
        {
            get { return this.Select(x => x.Item); }
        }

        #endregion

    #region ctor

        public SelectionList(IEnumerable<T> col)
            : base(toSelectionItemEnumerable(col))
        {

        }

        #endregion

    #region Public methods

        /// <summary>
        /// Adds the item to the list
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            int i = 0;
            foreach (T existingItem in AllItems)
            {
                if (item.CompareTo(existingItem) < 0) break;
                i++;
            }
            Insert(i, new SelectionItem<T>(item));
        }

        /// <summary>
        /// Checks if the item exists in the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(T item)
        {
            return AllItems.Contains(item);
        }

        /// <summary>
        /// Selects all the items in the list
        /// </summary>
        public void SelectAll()
        {
            foreach (SelectionItem<T> selectionItem in this)
            {
                selectionItem.IsSelected = true;
            }
        }

        /// <summary>
        /// Unselects all the items in the list
        /// </summary>
        public void UnselectAll()
        {
            foreach (SelectionItem<T> selectionItem in this)
            {
                selectionItem.IsSelected = false;
            }
        }

        #endregion

    #region Helper methods

        /// <summary>
        /// Creates an SelectionList from any IEnumerable
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private static IEnumerable<SelectionItem<T>> toSelectionItemEnumerable(IEnumerable<T> items)
        {
            List<SelectionItem<T>> list = new List<SelectionItem<T>>();
            foreach (T item in items)
            {
                SelectionItem<T> selectionItem = new SelectionItem<T>(item);
                list.Add(selectionItem);
            }
            return list;
        }

        #endregion
}

The SelectionList is basically an ObservableCollection of SelectionItem.

Now that you have a list of items that can be bound to each ListBoxItem I needed to figure out how to bind the IsSelected property of my items to the is IsSelected property of the ListBoxItem. I found the solution in

this post

in the MSDN Forums. You need to use this style:

<ListBox.ItemContainerStyle>
    <Style TargetType="{x:Type ListBoxItem}">
        <Setter Property="IsSelected" 
                Value="{Binding IsSelected}"/>
    </Style>
</ListBox.ItemContainerStyle>

Example:

Imagine you need a window with a list of sports from which you have to select the sports you like. In this window you need to be able to add items to the list and select and unselect all items at once. Here is the code for the window and the ViewModel:

<Window x:Class="MvvmChecklistBox.MultiSelectWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MvvmChecklistBox"
        Title="MultiSelectWindow" Height="300" Width="300">
    <Window.Resources>
        <local:MainWindowViewModel x:Key="viewModel"/>
    </Window.Resources>
    <StackPanel Margin="5" DataContext="{StaticResource viewModel}">
        <TextBlock>Sport:</TextBlock>
        <TextBox Name="textBoxNewSport"
                 Text="{Binding NewSport}"/>
        <Button Content="Add new sport"
                Command="{Binding AddCommand}"/>
        <ListBox Name="checkboxList"
                 ItemsSource="{Binding Sports}"
                 DisplayMemberPath="Item"
                 Margin="0,5" 
                 SelectionMode="Multiple">
            <ListBox.ItemContainerStyle>
                <Style TargetType="{x:Type ListBoxItem}">
                    <Setter Property="IsSelected" 
                            Value="{Binding IsSelected}"/>
                </Style>
            </ListBox.ItemContainerStyle>
        </ListBox>
        <Button Content="Select All"
                Margin="0,5"
                Command="{Binding SelectAllCommand}"/>
        <Button Content="Unselect All"
                Margin="0,5"
                Command="{Binding UnselectAllCommand}"/>
    </StackPanel>
</Window>
public class MainWindowViewModel : INotifyPropertyChanged
{
    #region Fields

        private ICommand _selectAllCommand;

        private ICommand _unselectAllCommand;

        private ICommand _addCommand;

        private string _newSport;

        #endregion

    #region Properties

        public SelectionList<string> Sports { get; set; }

        public string NewSport
        {
            get { return _newSport; }
            set
            {
                if (value == _newSport) return;
                _newSport = value;
                OnPropertyChanged("NewSport");
            }
        }

        #endregion

    #region Commands

        public ICommand SelectAllCommand
        {
            get
            {
                if (_selectAllCommand == null)
                {
                    _selectAllCommand = new RelayCommand(param => Sports.SelectAll());
                }
                return _selectAllCommand;
            }
        }

        public ICommand UnselectAllCommand
        {
            get
            {
                if (_unselectAllCommand == null)
                {
                    _unselectAllCommand = new RelayCommand(param => Sports.UnselectAll());
                }
                return _unselectAllCommand;
            }
        }

        public ICommand AddCommand
        {
            get
            {
                if (_addCommand == null)
                {
                    _addCommand = new RelayCommand(param =>
                    {
                        Sports.Add(NewSport);
                        NewSport = string.Empty;
                    });
                }
                return _addCommand;
            }
        }

        #endregion

    #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

    #region ctor

        public MainWindowViewModel()
        {
            string[] sports = { "Baseball", "Basketball", "Football", "Handball", "Soccer", "Volleyball" };
            Sports = new SelectionList<string>(sports);
        }

        #endregion

    #region Event invokers

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler changed = PropertyChanged;
            if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion
}

Here is the result:

MultiSelectWindow

The solution was really simple after I though of the SelectionList. I wanted however to do a little bit more. Instead of the simple Listbox I had to build a list of CheckBoxes. To good thing is that as far as the ViewModel is concerned we are all set, there's no need to change anything. All we need to change is the View (which is a Window in our case) using a DataTemple in the ListBox ItemTemplate to place a Checkbox instead of regular item.

<ListBox.ItemTemplate>
    <DataTemplate>
        <CheckBox IsChecked="{Binding IsSelected}" 
                  Content="{Binding Item}"/>
    </DataTemplate>
</ListBox.ItemTemplate>

Did it work? Kind of... It works but something weird happens because you can select all the checkboxes you want and then select the ListItem which gets highlighted. So now you have the checkbox selection and the item highlight selection and the two of them do not match. I don't want the ListBox item to get highlighted. Once again Google helped and I found a style to do just what I wanted in Alex Filo's blog. Here is the code:

<ListBox.ItemContainerStyle>
    <Style>
        <Setter Property="ListBoxItem.Background" Value="Transparent"/>
        <Setter Property="ListBoxItem.Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBoxItem}">
                    <Border x:Name="Bd" 
                    SnapsToDevicePixels="true" 
                    Background="{TemplateBinding Background}" 
                    BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter Property="Background" TargetName="Bd" Value="Transparent" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ListBox.ItemContainerStyle>

CheckBoxListError

Now we're done! Here is the final code for the view:

<Window x:Class="MvvmChecklistBox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="300">
    <StackPanel Margin="5">
        <TextBlock>Sport:</TextBlock>
        <TextBox Name="textBoxNewSport"
                 Text="{Binding NewSport}"/>
        <Button Content="Add new sport"
                Command="{Binding AddCommand}"/>
        <ListBox Name="checkboxList"
                 ItemsSource="{Binding Sports}"
                 Margin="0,5">
            <ListBox.ItemContainerStyle>
                <Style>
                    <Setter Property="ListBoxItem.Background" Value="Transparent"/>
                    <Setter Property="ListBoxItem.Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <Border x:Name="Bd" 
                                SnapsToDevicePixels="true" 
                                Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}" 
                                Padding="{TemplateBinding Padding}">
                                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsSelected" Value="true">
                                        <Setter Property="Background" TargetName="Bd" Value="Transparent" />
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding IsSelected}" 
                              Content="{Binding Item}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Button Content="Select All"
                Margin="0,5"
                Command="{Binding SelectAllCommand}"/>
        <Button Content="Unselect All"
                Margin="0,5"
                Command="{Binding UnselectAllCommand}"/>
    </StackPanel>
</Window>

I'm sure that someone has had this problem and the SelectionList seems like a straight forward solution. I haven't found anything like it but I'm sure I can't be the first to think of it. Well, I hope this helps you and if you find of think of a better solution please leave a comment.

Comments

Posted by: dc credit union
On: 11/30/2011 11:32:57 PM

really useful blog keep sharing
Posted by: Eric Ouellet
On: 3/11/2011 4:59:09 PM

I owe you a beer. Sorry. 10 beers !

Thanks a lot !
Eric

Looking for 4 hours... (very shy)
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}"
Content="{Binding Item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Posted by: Jeff
On: 6/4/2010 11:14:24 AM

Your right about not being the first one to try this concept. Josh Twist has an post showing pretty much the same type of concept. I've also developed something quite similar. Looks like we are all still reinventing the wheel a bit here thats why I'm looking around for a concensus implementation to contribute to a reusable library, perhaps one of the many MVVM frameworks on CodePlex.

http://www.thejoyofcode.com/ViewModels_and_CheckListBoxes.aspx
Posted by: AlexG
On: 5/19/2011 9:42:23 AM

Thanks a lot for concise, complete and easy-to-understand explanation of the subject. (Have you thought about writing a book on something you understand and enjoy? If "no" - think again! )))
Posted by: Monica
On: 5/3/2010 4:00:07 PM

Thanks so much!!! Your mention of the post that explained how to bind to the SelectedItem property on the bound object was just what I was looking for. I already had that property and needed to figure out how to bind it in MultiSelect mode.
Posted by: max24
On: 9/14/2011 9:05:57 AM

Fantastic work !!!!! you saved me like N number of days :)
Posted by: Christmas Gingerbread Recipes
On: 11/30/2011 7:41:03 AM

it is exactly that which i need keep sharing
Posted by: Petar
On: 1/20/2011 2:40:44 PM

How can I tell in MVVM fashion which items are checked as they are being checked?

Thanks!

Leave your comment

Author

Email (never displayed)

Website

Comment  
HTML is NOT allowed. Use regular line breaks and those will be respected.