42

I am implementing a listview, and a button next to it. I have to be able that when i select multiple items in a listview, and then click on a button, then the selected items are put into a list. But my question is , how do i bind the selected items towards the viewmodel? I changed my selectionmode to multiple. But then, do i just have to do:

SelectedItem={Binding path= selectedItems}

and then make in my viewmodel a property selectedItems, and it will set these items i have selected? Or what is the right solution to do this?

H.B.
  • 166,899
  • 29
  • 327
  • 400
Ruben
  • 1,033
  • 3
  • 12
  • 22
  • Does this answer your question? [VirtualizingStackPanel + MVVM + multiple selection](https://stackoverflow.com/questions/1273659/virtualizingstackpanel-mvvm-multiple-selection) – Herohtar Nov 24 '19 at 09:22

9 Answers9

49

Like Doctor has already pointed out, you can bind SelectedItems to XAML CommandParameter

After a lot of digging and googling, I have finally found 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 you XAML command databinding, define CommandParameter property BEFORE Command property. This a very time-consuming bug.

    enter image description here

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

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

For example, you can either send a listview/listbox's SelectedItems property to you ICommand methods or the listview/listbox it self. Great, isn't it?

Hope it prevents someone spending the huge amount of time I did to figure out how to receive SelectedItems as CanExecute parameter.

Community
  • 1
  • 1
Julio Nobre
  • 4,196
  • 3
  • 46
  • 49
  • 3
    IMO this post should be marked as best answer - simple and leverages the API. It's not obvious that attribute order can be significant, that's a nice find. – Paul Williams Oct 17 '14 at 14:39
  • This approach does fit the OP question, but to have true data binding, please check my answer in this thread. It doesn't require button click or any event and pass as command parameter, it simply work like any other control that support data binding. Which i think, is a much better approach. – techhero May 05 '16 at 18:39
  • 1
    I got stuck on this one because `SelectedItems` is in `System.Windows.Controls`, which I don't reference from my ViewModels. The solution was to make the expected command parameter type be `System.Collections.IEnumerable`, and then `Cast<>` it. – Benjol Dec 14 '22 at 09:19
24

It's kind of tricky to do this Mutliple Selection in MVVM, because the SelectedItems property isn't a Dependency Property. However, there are some tricks you can use. I found this triology of blog posts that describe the matter in some details and provide some useful solutions.

Hope this helps

AbdouMoumen
  • 3,814
  • 1
  • 19
  • 28
20

If you are using System.Windows.Interactivity and Microsoft.Expression.Interactions already, here is a workaround without any other code/behaviour to mess around. If you need these, it can be download from here

This workaround make use of interactivity event trigger and interactions set property mechanism in above assemblies.

Additional namespace declaration in XAML

xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

XAML:

<ListView Name="MyListView" ItemsSource="{Binding ModelList}" DisplayMemberPath="Name"  Grid.Column="0">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
      <ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}" PropertyName="SelectedItems" Value="{Binding Path=SelectedItems, ElementName=MyListView}"/>
    </i:EventTrigger>
  </i:Interaction.Triggers>
</ListView>

View Model:

public class ModelListViewModel
{
  public ObservableCollection<Model> ModelList { get; set; }
  public ObservableCollection<Model> SelectedModels { get; set; }

  public ModelListViewModel() {
    ModelList = new ObservableCollection<Model>();
    SelectedModels = new ObservableCollection<Model>();
  }

  public System.Collections.IList SelectedItems {
    get {
      return SelectedModels;
    }
    set {
      SelectedModels.Clear();
      foreach (Model model in value) {
        SelectedModels.Add(model);
      }
    }
  }
}

In example above, your ViewModel will pick up the selected items whenever the selection on ListView changed.

techhero
  • 1,074
  • 11
  • 11
  • 3
    The required packages for this solution are also available on nuget: https://www.nuget.org/packages/Blend.Interctivity.WPF.v4.0/1.0.2 – Zrethreal Jul 31 '16 at 15:10
12

What you can do is you can handle the Button_Click(...) in your code-behind. Then in that code-behind method you can create a List of selected items by iterating over the selected items of the listView.

Since it is allowed to access the ViewModel from the View you can now call a method on your ViewModel and pass the list of selected items as a parameter.

I'm not sure if this would also work with Bindings only, however it is not bad practice to use code-behind as well.

Example Code:

public void Button_Click(object sender, EventArguments arg)
{
  List<ListViewItem> mySelectedItems = new List<ListViewItem>();

  foreach(ListViewItem item in myListView.SelectedItems)
  {
    mySelectedItems.Add(item);
  }

  ViewModel.SomeMethod(mySelectedItems);
}

EDIT

Here is a minimalist example, XAML:

<DataTemplate
            x:Key="CarTemplate"
            DataType="{x:Type Car}">
</DataTemplate>

<ListView x:Name="myListView"
          ItemsSource="{Binding Path=Cars}"
          ItemTemplate="{StaticResource CarTemplate}">
</ListView>

CODE-BEHIND:

public void Button_Click(object sender, EventArguments arg)
    {
      List<Car> mySelectedItems = new List<Car>();

      foreach(Car item in myListView.SelectedItems)
      {
        mySelectedItems.Add(item);
      }

      ViewModel.SomeMethod(mySelectedItems);
    }
Community
  • 1
  • 1
Christian
  • 4,345
  • 5
  • 42
  • 71
  • 1
    How do you parse ListViewItem to a particular object? because I can't do anything with listviewItem? – Ruben Apr 21 '11 at 08:13
  • 1
    Well if your ListViewItem is of type MyType you can just use that: List mySelectedItems = new List(); foreach(MyType mytype in myListView.SelectedItems) ... – Christian Apr 21 '11 at 08:14
  • 1
    @Ruben - I have added a simple example. Good Luck :) – Christian Apr 21 '11 at 08:34
  • 1
    Thanks a lot, but i also want to change the font, i do this:foreach (ListViewItem x in DropListView.SelectedItems) { x.FontStyle = FontStyles.Oblique; } – Ruben Apr 21 '11 at 08:47
  • 1
    But he gives error, because SelectedItems is now not an object of ListViewItem? – Ruben Apr 21 '11 at 08:48
  • 1
    @Ruben - Yes, because now you have your concrete class, like in my example: Car. What you can do is you can get your ListViewItem back. I will provide some code in a minute (need to look it up). – Christian Apr 21 '11 at 08:54
  • 1
    @Ruben - Try: ListViewItem item = myListView.ItemContainerGenerator.ContainerFromItem(myCar) as ListViewItem; Here "myCar" is the actual object you get from the list view. This will give you a ListViewItem whose FontStyle you can change. – Christian Apr 21 '11 at 08:57
6

Unfortunately the SelectedItems is a read only not bindable property.

I found a lot of help from this article How to Databind to a SelectedItems property in WPF

Dummy01
  • 1,985
  • 1
  • 16
  • 21
3

If you are using Metro/WinRT you may want to look at the WinRTXXAMLToolkit as it offers a bindable SelectedItems dependency property as one of its extensions.

Tejas Sharma
  • 3,420
  • 22
  • 35
technicalflaw
  • 1,219
  • 12
  • 8
3

You can't bind, but you can send to Command as an CommandParameter.

Doctor
  • 788
  • 9
  • 12
0

As a slight variation on Christian's post, I implemented similar code using the ListView.SelectionChanged event. Instead of calling a method on the ViewModel, I set a property called SelectedItems:

public void ListView_SelectionChanged( object s, SelectionChangedEventArgs e ) {
    List<Car> mySelectedItems = new List<Car>();

    foreach( Car item in myListView.SelectedItems )
        mySelectedItems.Add(item);

    ViewModel.SelectedItems = mySelectedItems;
}

This way, ViewModel.SelectedItems is available for any command you might have in your ViewModel and it can be used for data binding (if you turn it into an ObservableCollection).

Mitkins
  • 4,031
  • 3
  • 40
  • 77
  • 2
    This is not a good practice as MVVM really does not promote code behind unless no other solution around. – techhero May 05 '16 at 18:42
0

I did a solution for this, to me this was simple enough.

<ListBox ItemsSource="{Binding ListOfModel}" x:Name="ModelList"
                                SelectedItem="{Binding SelectedModel, Mode=TwoWay}">
                            <i:Interaction.Triggers>
                                <i:EventTrigger EventName="SelectionChanged">
                                    <i:InvokeCommandAction Command="{Binding ExecuteListBoxSelectionChange}" CommandParameter="{Binding ElementName=ModelList}">
                                    </i:InvokeCommandAction>
                                </i:EventTrigger>
                            </i:Interaction.Triggers>
                        </ListBox>

Then in the viewmodel:

public ICommand ExecuteListBoxSelectionChange { get; private set; }
ExecuteListBoxSelectionChange = DelegatingCommand<ListBox>.For(ListBoxSelectionChnageEvent).AlwaysEnabled();

SelectedModels is the list where I wanted the selection to be filled.

    private void ListBoxSelectionChnageEvent(ListBox modelListBox)
    {
        List<ModelInfo> tempModelInfo = new List<ModelInfo>();
         foreach(ModelInfo a in modelListBox.SelectedItems)
             tempModelInfo.Add(a);

         SelectedModels = tempModelInfo;
    }
Manas S
  • 1
  • 1