61

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?

Paul Alexander
  • 31,970
  • 14
  • 96
  • 151

4 Answers4

95

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); }
}
ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Josh G
  • 14,068
  • 7
  • 62
  • 74
  • 31
    Please note that this solution is not working when using a VirtualizingStackPanel in the ListBox (which is the default). More info at this post : http://stackoverflow.com/questions/1273659/virtualizingstackpanel-mvvm-multiple-selection – decasteljau Aug 13 '09 at 19:18
  • 3
    Good catch. Thanks for the update. For single selection, the best solution is ICollectionView. Microsoft needs to create an ICollectionView interface that supports multi-selection. – Josh G Aug 27 '09 at 19:14
  • 5
    If you are using a virtualizing panel, you can't use this method. A work around is to handle the SelectionChanged event in the code behind of the view. It's very easy to then pass the selection on to the VM in code... the event handler gives you all of the items that were newly selected AND unselected. – Josh G Apr 07 '11 at 12:34
  • 1
    @JoshG That's fine for binding from the `ListView` to the viewmodel, but it still doesn't address the need to bind from the viewmodel to the `ListView`. If a viewmodel that's currently scrolled out of view has its `IsSelected` property set to true, the `ListView` will remain blissfully unaware of this. If you then have any code that relies on the SelectedItems property of the ListView, this will miss the viewmodel whose IsSelected has been set programmatically, so to speak. I don't have a simple workaround for this, but just wanted to point out the limitation of only handling SelectionChanged. – Mal Ross Mar 05 '12 at 16:47
  • @Mal Ross: With virtualizing, this is a difficult problem to solve. The best solution from my perspective is to avoid the problem altogether. If you must reference the selected items from code, always reference the collection on the VM. This makes sense anyway... the VM is really the state of the view. The ListView should only be used for displaying the state contained in the VM. Code that uses the selected items should always query the VM not the view. – Josh G Mar 26 '12 at 15:18
  • I've done this so many times, but each time forget how I did it. Thanks! – Billy Jake O'Connor Oct 27 '17 at 10:37
12

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.

Community
  • 1
  • 1
Julio Nobre
  • 4,196
  • 3
  • 46
  • 49
  • 2
    I just tried this with a DataGrid, using the SelectedItems property in this manner I found that the CanExecute was being called with the _previous_ value if the SelectedItems (i.e. it did not reflect the current state of the selection). May be different for other controls, but proved useless in this case. – Rob G Nov 25 '14 at 10:24
  • 1
    Worked fine for me (for both ListBox and DataGrid, using .Net 4.0). I had the Command and CommandParameter in the reverse order to what was specified and it still worked. – BCA Mar 13 '15 at 17:38
  • 2
    nice, perfect for listview on 4.5.2. What to add is that I've also used DelegateCommand from Prism for the Execute and CanExecute.so: `ICommand btnCommand = new DelegateCommand(Execute,CanExecute);` – CularBytes Mar 25 '16 at 12:41
  • unbelievable! I tried so many times, sometimes it worked, sometimes it doesn't.... and it was all about the order... – Florian Jan 10 '17 at 12:17
3

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 .

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
MichaelLo
  • 1,289
  • 1
  • 14
  • 26
  • 1
    I like your solution a lot, however I had to modify your code so that it didn't cache the list box in the attached property. Caching the list box prevents you from using the attached property on any other list boxes. – Darkhydro Jun 19 '14 at 21:22
1

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

mcalex
  • 6,628
  • 5
  • 50
  • 80
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
  • hmm it actually only goes one way with this method (view > view model). not capable of view model > view as described here – Simon_Weaver Jul 25 '10 at 04:23
  • 3
    Whilst this may theoretically answer the question, [it would be preferable](http://meta.stackexchange.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Kev Nov 12 '11 at 00:24