60
votes

On my journey to learning MVVM I've established some basic understanding of WPF and the ViewModel pattern. I'm using the following abstraction when providing a list and am interested in a single selected item.

public ObservableCollection<OrderViewModel> Orders { get; private set; }
public ICollectionView OrdersView
{
    get
    {
        if( _ordersView == null )
            _ordersView = CollectionViewSource.GetDefaultView( Orders );
        return _ordersView;
    }
}
private ICollectionView _ordersView;

public OrderViewModel CurrentOrder 
{ 
    get { return OrdersView.CurrentItem as OrderViewModel; } 
    set { OrdersView.MoveCurrentTo( value ); } 
}

I can then bind the OrdersView along with supporting sorting and filtering to a list in WPF:

<ListView ItemsSource="{Binding Path=OrdersView}" 
          IsSynchronizedWithCurrentItem="True">

This works really well for single selection views. But I'd like to also support multiple selections in the view and have the model bind to the list of selected items.

How would I bind the ListView.SelectedItems to a backer property on the ViewModel?

4

4 Answers

95
votes

Add an IsSelected property to your child ViewModel (OrderViewModel in your case):

public bool IsSelected { get; set; }

Bind the selected property on the container to this (for ListBox in this case):

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

IsSelected is updated to match the corresponding field on the container.

You can get the selected children in the view model by doing the following:

public IEnumerable<OrderViewModel> SelectedOrders
{
    get { return Orders.Where(o => o.IsSelected); }
}
12
votes

I can assure you: SelectedItems is indeed bindable as a XAML CommandParameter

There is a simple solution to this common issue; to make it work you must follow ALL the following rules:

  1. Following Ed Ball's suggestion, on your XAML command databinding, define the CommandParameter attribute BEFORE the Command attribute. This a very time-consuming bug.

    enter image description here

  2. Make sure your ICommand's CanExecute and Execute methods have a parameter of type object. This way you can prevent silenced cast exceptions that occur whenever the databinding's CommandParameter type does not match your Command method's parameter type:

    private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)  
    {
         // Your code goes here
    }
    
    private bool OnDeleteSelectedItemsExecute(object SelectedItems)  
    {
        // Your code goes here
    }
    

For example, you can either send a ListView/ListBox's SelectedItems property to your ICommand methods or the ListView/ListBox itself. Great, isn't it?

I hope this prevents someone from spending the huge amount of time I did to figure out how to receive SelectedItems as a CanExecute parameter.

3
votes

One can try creating an attached property.

Doing so will save one from adding the IsSelected property for each and every list you bind. I have done it for ListBox, but it can be modified for use a in a list view.

<ListBox SelectionMode="Multiple"
         local:ListBoxMultipleSelection.SelectedItems="{Binding SelectedItems}" >

More info: WPF – Binding ListBox SelectedItems – Attached Property VS Style .

1
votes

If you're using MVVM-LIGHT you can use this pattern:

https://galasoft.ch/posts/2010/05/handling-datagrid-selecteditems-in-an-mvvm-friendly-manner

Not especially elegant but looks like it should be reliable at least