Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Managing multiple selections with MVVM

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?

like image 752
Paul Alexander Avatar asked Apr 29 '09 16:04

Paul Alexander


4 Answers

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); }
}
like image 127
Josh G Avatar answered Sep 22 '22 16:09

Josh G


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.

like image 20
Julio Nobre Avatar answered Sep 20 '22 16:09

Julio Nobre


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 .

like image 31
MichaelLo Avatar answered Sep 22 '22 16:09

MichaelLo


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

like image 35
Simon_Weaver Avatar answered Sep 22 '22 16:09

Simon_Weaver