Posted by Mark Withall: 2017-05-14

A common problem that people face using the Model-View-ViewModel (MVVM) pattern is handling multiple selected items in a ListBox with extended selection mode turned on.

Let’s have a look at an example.

The ViewModel

We have a list of items in an ObservableCollection (so that the View updates if we modify the content of the collection). We also have a placeholder for some command that will operate on the selected items.

public class MainPresenter : Presenter
{
    public ObservableCollection<ItemPresenter> Items { get; }
        = new ObservableCollection<ItemPresenter>
    {
        new ItemPresenter("A"),
        new ItemPresenter("B"),
        new ItemPresenter("C"),
        new ItemPresenter("D")
    };

    public ICommand DoStuffCommand => new Command(param => {  });
}

For this example, each item is just a wrapped string. The item itself looks like this.

public class ItemPresenter : Presenter
{
    private readonly string _value;

    public ItemPresenter(string value)
    {
        _value = value;
    }

    public override string ToString()
    {
        return _value;
    }
}

The View

The basic View is a ListBox, with the selection mode set to extended (so we can select multiple items using ctrl/shift click or the keyboard equivalents). We also have a button that we’d like to perform an action on the selected items.

<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended" />
<Button Command="{Binding DoStuffCommand}">Do Stuff</Button>

The MVVM Framework

For completeness, here is the implementation of a basic ViewModel base class and a simple ICommand implementation.

public abstract class Presenter : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class Command : ICommand
{
    private readonly Action<object> _action;

    public event EventHandler CanExecuteChanged { add { } remove { } }

    public Command(Action<object> action)
    {
        _action = action;
    }

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter)
    {
        _action?.Invoke(parameter);
    }
}

How We’d Like To Do It

In an ideal world, we’d like to bind an ObservableCollection directly to the SelectedItems property of the ListBox. Alas, this is not currently possible.

In MainPresenter:

public ObservableCollection<ItemPresenter> SelectedItems { get; }
    = new ObservableCollection<ItemPresenter>();

In MainWindow.xaml:

<ListBox ItemsSource="{Binding Items}"
         SelectionMode="Extended"
         SelectedItems="{Binding SelectedItems}" />

One-way To Presenter

If we only care about setting the selected items from the View and operating on it when a command it executed, we can do this relatively simply.

First we need to give the ListBox a name.

<ListBox x:Name="ListBox" ItemsSource="{Binding Items}" SelectionMode="Extended" />

And then we can add a command parameter that points at the ListBox SelectedItems property. The property is an IList (non-generic) containing ItemPresenter references.

<Button Command="{Binding DoStuffCommand}"
        CommandParameter="{Binding ElementName=ListBox, Path=SelectedItems}">Do Stuff</Button>

Now our command will receive the list of selected ItemPresenter objects as a parameter and we can do as we wish with them.

Two-way Binding

If we want two-way binding, things get a little more messy.

Firstly, we need to add a property to ItemPresenter to store the selected state of the item.

private bool _isSelected;
public bool IsSelected
{
    get { return _isSelected; }
    set
    {
        _isSelected = value;
        RaisePropertyChanged();
    }
}

And then we have to find a way of binding that property to the View. The easiest way to do this is to create a style for a ListBoxItem and bind the IsSelected property there.

<ListBox ItemsSource="{Binding Items}" SelectionMode="Extended">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

Now we can set the selected items from the ViewModel as well as from the View.

When we execute the command, we can access the selected items using a short LINQ query:

Items.Where(i => i.IsSelected);

Update

David Hewson has kindly pointed out that this two-way binding workaround doesn’t play nicely with virtualization of the ListBox.

There are two options available here. Firstly, we could disable virtualization on the ListBox.

<ListBox VirtualizingStackPanel.IsVirtualizing="False" ... />

This is fine, as long as we aren’t expecting the list of items to grow too large.

A second, more extreme workaround is to override the ‘select all’ command for the ListBox and set the selected items via the ViewModel. This involves intercepting Ctrl+A and calling a command on the ViewModel.

public ICommand SelectAllCommand => new Command(_ =>
{
    foreach (var item in Items)
    {
        item.IsSelected = true;
    }
});
<ListBox.InputBindings>
    <KeyBinding Gesture="Ctrl+A" Command="{Binding SelectAllCommand}" />
</ListBox.InputBindings>