How to bind multiple selection of listview to view

2019-03-09 00:29发布

问题:

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?

回答1:

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);
    }


回答2:

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.

  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.



回答3:

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.

  • Part I
  • Part II
  • Part III

Hope this helps



回答4:

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.



回答5:

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



回答6:

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



回答7:

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.



回答8:

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;
    }


回答9:

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).