如何将Listview的多项选择与ViewModel绑定?

44

我正在实现一个ListView和一个相应的按钮。我需要能够在选择了多项ListView中的项目后,点击按钮将选定的项目放入列表中。但我的问题是,如何将选定的项目绑定到ViewModel? 我将SelectionMode更改为Multiple。那么,我只需要执行以下操作吗:

SelectedItem={Binding path= selectedItems}

然后在我的视图模型中创建一个selectedItems属性,它会设置我所选的这些项?或者有什么正确的解决方案可以做到这一点?


这个回答解决了你的问题吗?VirtualizingStackPanel + MVVM + 多选 - Herohtar
9个回答

49

就像Doctor已经指出的一样,你可以将SelectedItems绑定到XAML CommandParameter

经过大量挖掘和搜索,我终于找到了一个简单的解决方案来解决这个常见问题。

要使它工作,您必须遵循以下所有规则

  1. 按照Ed Ball的建议,在您的XAML命令数据绑定中,在定义Command属性之前定义CommandParameter属性。这是一个非常耗时的错误。

    输入图像说明

  2. 确保您的ICommandCanExecuteExecute方法具有object类型的参数。这样,您就可以防止CommandParameter类型与您的命令方法的参数类型不匹配时发生的静默转换异常。

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

例如,您可以将列表视图/Listbox的 SelectedItems 属性发送到您的 ICommand 方法或列表视图/Listbox本身。很棒,不是吗?

希望它可以防止有人像我一样花费巨额时间来弄清楚如何将 SelectedItems 作为 CanExecute 参数接收。


3
我认为这篇帖子应该被标记为最佳答案——简单易懂且利用了API。属性顺序可能很重要,这是一个很好的发现,不太明显。 - Paul Williams
这种方法确实适合OP的问题,但为了实现真正的数据绑定,请查看我在此线程中的答案。它不需要按钮点击或任何事件,并且作为命令参数传递,它就像支持数据绑定的任何其他控件一样工作。我认为这是一个更好的方法。 - techhero
1
我卡在这里了,因为SelectedItemsSystem.Windows.Controls中,而我在ViewModels中没有引用它。解决方案是将预期的命令参数类型设置为System.Collections.IEnumerable,然后使用Cast<>进行转换。 - Benjol

24

在MVVM中进行多选有些棘手,因为SelectedItems属性不是Dependency Property。 不过,有一些技巧可以使用。 我发现了这三部分博客文章,详细描述了这个问题并提供了一些有用的解决方案。

希望这有所帮助。


问题在于这些帖子没有完整的代码(下载无法使用),因此目前有点无用... - etwas77
@etwas77 第三个仍然可以正常工作(这是你真正需要的唯一一个)。 - ghord
@ghord: 它不支持双向绑定。例如,如果您设置了SelectedItems,它不会更新ListView。 - viking_grll

21
如果您已经在使用System.Windows.Interactivity和Microsoft.Expression.Interactions,那么这里有一个解决方法,无需其他代码/行为混淆。如果您需要这些内容,可以从这里下载。
此解决方法利用上述程序集中的交互事件触发器和交互设置属性机制。
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>

视图模型:

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

在上面的示例中,无论何时ListView上的选择发生更改,您的ViewModel都将获取所选项。


3
此解决方案所需的包也可以在NuGet上获取:https://www.nuget.org/packages/Blend.Interctivity.WPF.v4.0/1.0.2 - Zrethreal
虽然这对我来说有效,但请知道这实际上是一种单向绑定。如果我销毁了我的视图但保持VM活动,并且创建了一个新的视图实例并将其绑定到该VM,ListBox不会更新所选项目。我必须手动捕获ListBoxLoaded事件,并在其中对我的SelectModels属性运行循环,逐个将所有这些模型添加到ListBox的SelectedItems中。 - undefined

12

您可以在代码后台处理Button_Click(...)。然后在此代码后台方法中,通过遍历listView的选定项创建所选项列表。

由于允许从视图访问ViewModel,因此现在可以在ViewModel上调用一个方法并将所选项列表作为参数传递。

我不确定这是否仅适用于绑定,但使用代码后台也不是不好的做法。

示例代码:

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

编辑

以下是一个极简示例,使用XAML:

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

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

后台代码:

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

1
你如何将ListViewItem解析为特定对象?因为我无法使用ListViewItem进行任何操作。 - Ruben
1
如果您的ListViewItem是MyType类型,您可以直接使用它:List<MyType> mySelectedItems = new List<MyType>(); foreach (MyType mytype in myListView.SelectedItems) ... - Christian
1
@Ruben - 我已经添加了一个简单的例子。祝你好运 :) - Christian
1
但是他会报错,因为SelectedItems现在不是ListViewItem对象了? - Ruben
1
@Ruben - 试试这个:ListViewItem item = myListView.ItemContainerGenerator.ContainerFromItem(myCar) as ListViewItem; 这里的“myCar”是你从列表视图中获取的实际对象。这将给你一个ListViewItem,你可以更改它的字体样式。 - Christian
显示剩余2条评论

6

3
您不能进行绑定,但您可以将Command作为CommandParameter发送。

3
如果你正在使用 Metro/WinRT,你可能想看一下 WinRTXXAMLToolkit,因为它提供了一个可绑定的 SelectedItems 依赖属性作为其扩展之一。

0

我为此提供了一个解决方案,对我来说这非常简单。

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

然后在视图模型中:

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

SelectedModels 是我想要填充选择的列表。

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

         SelectedModels = tempModelInfo;
    }

0
作为对Christian帖子的轻微变化,我使用了ListView.SelectionChanged事件实现了类似的代码。我没有在ViewModel上调用方法,而是设置了一个名为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;
}

这样,ViewModel.SelectedItems 可以在 ViewModel 中的任何命令中使用,并且可以用于数据绑定(如果将其转换为 ObservableCollection)。


2
这并不是一种好的实践,因为MVVM真的不提倡使用代码后台,除非没有其他解决方案。 - techhero

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接