在MVVM WPF项目中从DataGrid中选择多个项

67
在MVVM WPF项目中,如何从DataGrid中选择多个项?
9个回答

118

您可以简单地添加一个自定义依赖属性来实现此功能:

public class CustomDataGrid : DataGrid
{
    public CustomDataGrid ()
    {
        this.SelectionChanged += CustomDataGrid_SelectionChanged;
    }

    void CustomDataGrid_SelectionChanged (object sender, SelectionChangedEventArgs e)
    {
        this.SelectedItemsList = this.SelectedItems;
    }
    #region SelectedItemsList

    public IList SelectedItemsList
    {
        get { return (IList)GetValue (SelectedItemsListProperty); }
        set { SetValue (SelectedItemsListProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemsListProperty =
            DependencyProperty.Register ("SelectedItemsList", typeof (IList), typeof (CustomDataGrid), new PropertyMetadata (null));

    #endregion
}

现在你可以在 XAML 中使用这个 dataGrid

<Window x:Class="DataGridTesting.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:DataGridTesting.CustomDatagrid"
    Title="MainWindow"
    Height="350"
    Width="525">
  <DockPanel>
    <local:CustomDataGrid ItemsSource="{Binding Model}"
        SelectionMode="Extended"
        AlternatingRowBackground="Aquamarine"
        SelectionUnit="FullRow"
        IsReadOnly="True"
        SnapsToDevicePixels="True"
        SelectedItemsList="{Binding TestSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
  </DockPanel>
</Window>

我的 ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    private static object _lock = new object ();
    private List<MyModel> _myModel;

    public IEnumerable<MyModel> Model { get { return _myModel; } }

    private IList _selectedModels = new ArrayList ();

    public IList TestSelected
    {
        get { return _selectedModels; }
        set
        {
            _selectedModels = value;
            RaisePropertyChanged ("TestSelected");
        }
    }

    public MyViewModel ()
    {
        _myModel = new List<MyModel> ();
        BindingOperations.EnableCollectionSynchronization (_myModel, _lock);

        for (int i = 0; i < 10; i++)
        {
            _myModel.Add (new MyModel
            {
                Name = "Test " + i,
                Age = i * 22
            });
        }
        RaisePropertyChanged ("Model");
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged (string propertyName)
    {
        var pc = PropertyChanged;
        if (pc != null)
            pc (this, new PropertyChangedEventArgs (propertyName));
    }
}

我的模型:

public class MyModel
{
    public string Name { get; set; }
    public int Age { get; set; }
}

最后,这是MainWindow背后的代码:

public partial class MainWindow : Window
{
    public MainWindow ()
    {
        InitializeComponent ();
        this.DataContext = new MyViewModel ();
    }
}

我希望这个清晰的MVVM设计有所帮助。


1
看起来你得到了答案 :) - Sandesh
如果您想在TestSelected的setter中检查新选择的项目列表是否等于先前的列表,则此方法将无效。原因是CustomDataGrid_SelectionChanged中的SelectedItemsList和SelectedItemseason都包含对列表的相同引用。因此它们始终相等。 - tabina
@tabina 你说得对,这个解决方案仅在您想获取所有__当前__选定项目时有用,如果您想将其与旧选择的项目进行比较,则无用。您可以修改依赖属性的PropertyMetadata以包含一个可以执行比较的函数。 - Sandesh
12
“this.SelectedItemsList = this.SelectedItems;” 对我来说没有起作用,因为“SelectedItemsList”总是被设置为“null”。然而,将代码更改为“foreach (var item in this.SelectedItems) { this.SelectedItemsList.Add(item); }”就成功了。请注意,这需要您先调用“this.SelectedItemsList.Clear();”,以便“SelectedItemsList”的项目不会重复。 - M463
1
为什么你有SelectedItemsList的双向绑定?我认为你的代码无法处理从其源(VM)更改SelectedItems属性。 - Lukáš Koten
显示剩余5条评论

30

我会使用System.Windows.Interactivity创建Behaviors。您需要在项目中手动引用它。

假设有一个控件没有公开的SelectedItems(例如,ListBox,DataGrid)

您可以创建一个类似以下代码的行为类

public class ListBoxSelectedItemsBehavior : Behavior<ListBox>
{
    protected override void OnAttached()
    {
        AssociatedObject.SelectionChanged += AssociatedObjectSelectionChanged;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.SelectionChanged -= AssociatedObjectSelectionChanged;
    }

    void AssociatedObjectSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        var array = new object[AssociatedObject.SelectedItems.Count];
        AssociatedObject.SelectedItems.CopyTo(array, 0);
        SelectedItems = array;
    }

    public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.Register("SelectedItems", typeof(IEnumerable), typeof(ListBoxSelectedItemsBehavior), 
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

    public IEnumerable SelectedItems
    {
        get { return (IEnumerable)GetValue(SelectedItemsProperty); }
        set { SetValue(SelectedItemsProperty, value); }
    }
}

在您的XAML中,我会像这样执行Binding,其中ixmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"behaviors是您的Behavior类的命名空间。

<ListBox>
 <i:Interaction.Behaviors>
    <behaviors:ListBoxSelectedItemsBehavior SelectedItems="{Binding SelectedItems, Mode=OneWayToSource}" />
 </i:Interaction.Behaviors>
假设您的ListBoxDataContextViewModel中具有SelectedItems属性,则它将自动更新SelectedItems。您已经从View封装了event订阅,即:
<ListBox SelectionChanged="ListBox_SelectionChanged"/>

如果您想要的话,可以将Behavior类更改为DataGrid类型。


@III 这里的 <ListBox SelectionChanged="ListBox_SelectionChanged> 的目的是什么? - Manvinder
@MegaMind 我想强调的是,当你使用行为时,就不需要SelectionChanged事件。 - 123 456 789 0
1
@III 我已经尝试了你的解决方案,但在视图模型中,选定的项目始终为空。我编译了一个简单的解决方案来展示你的代码。如果你能看一下那个链接就好了。https://www.dropbox.com/s/lh4zrhnatpchqzi/MultiSelectedDataGrid.zip?dl=0 - Manvinder
2
@III - 是的,我太傻了,引用了相同的答案。现在我无法编辑我的错误评论,所以我已经删除了它 - 这个是对我有用的:https://dev59.com/yGsz5IYBdhLWcg3wJUfJ#8088926 。就像我在旧评论中说的那样(现在已删除),你的答案对我没有用,SelectedItems总是为空,就像MegaMind一样。另一个答案几乎和你的一样,但对我有效。 - Edward
1
只要 SelectedItemsIEnumerable 类型,我也得到了 null。但是一旦类型更改为 IList,数据就出现了。不幸的是,绑定并不真正是双向的。解决方案是将这个答案和这篇博客文章结合起来。 - Raphael Müller
显示剩余4条评论

23

我在我的应用程序中使用了这个解决方案:

XAML:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="SelectionChanged">
         <i:InvokeCommandAction Command="{Binding SelectItemsCommand}" CommandParameter="{Binding Path=SelectedItems,ElementName=TestListView}"/>
     </i:EventTrigger>
</i:Interaction.Triggers>
在您的XAML文件的顶部添加以下代码行:

在您的 XAML 文件的顶部添加此代码行:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

SelectedItemsCommand 是 ICommand 类型,它被编写在你的视图模型中。

使用的 DLL:

System.Windows.Interactivity.dll


12

使用WPF的默认DataGrid时,无法像SelectedItem属性那样使用绑定,因为SelectedItems属性不是一个DependencyProperty。

实现你想要的功能的一种方法是注册DataGrid的SelectionChanged事件以更新ViewModel中存储所选项的属性。

DataGrid的SelectedItems属性的类型是IList,因此需要将列表中的项转换为特定类型。

C#

public MyViewModel {
  get{
    return this.DataContext as MyViewModel;
  }
}

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  // ... Get SelectedItems from DataGrid.
  var grid = sender as DataGrid;
  var selected = grid.SelectedItems;

  List<MyObject> selectedObjects = selected.OfType<MyObject>().ToList();

  MyViewModel.SelectedMyObjects = selectedObjects;
}

XAML

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
    <DataGrid
        SelectionChanged="DataGrid_SelectionChanged"
        />
    </Grid>
</Window>

1
谢谢您的回答,但我正在寻找纯粹基于MVVM的解决方案,不涉及代码后台。 - Manvinder
我感谢这个答案。尽管它不是 MVVM,但它是第一个提到为什么我无法在 XAML 中访问“SelectedItems”属性的答案。 - Steffen Winkler
2
它完全不会破坏你的MVVM模型。你仍然在你的VM中拥有一个纯净的SelectedMyObjects属性,它对View或其如何设置一无所知。仅仅因为View中有一些代码并不意味着它不是纯粹的MVVM。 - Bill Lefler

2

您可以在模型中添加“IsSelected”属性,并在行中添加复选框。


2
您可以创建一个可重用的通用基类。这样,您就可以从代码和UI中选择行。
这是我想要可选择的示例类。
public class MyClass
{
    public string MyString {get; set;}   
}

为可选择的类创建通用基类。INotifyPropertyChanged使UI在设置IsSelected时更新。

public class SelectableItem<T> : System.ComponentModel.INotifyPropertyChanged
{
    public SelectableItem(T item)
    {
        Item = item;
    }

    public T Item { get; set; }

    bool _isSelected;

    public bool IsSelected {
        get {
            return _isSelected;
        }
        set {
            if (value == _isSelected)
            {
                return;
            }

            _isSelected = value;

            if (PropertyChanged != null)
            { 
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("IsSelected"));
            }
        }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}

创建可选择的类
public class MySelectableItem: SelectableItem<MyClass>
{
    public MySelectableItem(MyClass item)
       :base(item)
    {
    }
}

创建一个属性来绑定到。
ObservableCollection<MySelectableItem> MyObservableCollection ...

设置属性

MyObservableCollection = myItems.Select(x => new MySelectableItem(x));

将数据绑定到数据网格,并在DataGridRow上添加样式,该样式绑定到MySelectedItem上的IsSelected属性。

<DataGrid  
    ItemsSource="{Binding MyObservableCollection}"
    SelectionMode="Extended">
    <DataGrid.Resources>
        <Style TargetType="DataGridRow">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </DataGrid.Resources>
</DataGrid>

获取选定的行/项

var selectedItems = MyObservableCollection.Where(x=>x.IsSelected).Select(y=>y.Item);

选择行/项目
MyObservableCollection[0].IsSelected = true;

编辑——> 当启用行虚拟化时,似乎不起作用。

很遗憾,当当前不可见的行(因为它们已经滚动出视图)被选择或取消选择时,这对我来说并不可靠。 - Martin
当EnableRowVirtualization为true时,似乎它不起作用。 - AxdorphCoder

1

我的解决方案与Sandesh几乎相同。然而,我没有使用CustomDataGrid来解决这个问题。相反,我在视图模型中使用了一个加号按钮点击事件和适当的函数。在我的代码中,主要的重点是能够从绑定到PeopleList属性(ObservableCollection)的Datagrid中删除多个人对象。

这是我的模型:

 public class Person
    {
        public Person()
        {

        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }

    }
}

这是我的 ViewModel(仅包含必要的部分):

public class PersonViewModel : BindableBase
    {
        private ObservableCollection<Person> _peopleList;
        // to be able to delete or save more than one person object
        private List<Person> _selectedPersonList;

        //MyICommand
        public MyICommand AddCommand { get; set; }
        public MyICommand DeleteCommand { get; set; }

        private string _firstName;
        private string _lastName;
        private int _age;

        public PersonViewModel()
        {
            _peopleList = new ObservableCollection<Person>();
            LoadPerson();
            AddCommand = new MyICommand(AddPerson);
            DeleteCommand = new MyICommand(DeletePerson, CanDeletePerson);
            // To be able to delete or save more than one person
            _selectedPersonList = new List<Person>();
        } 
public ObservableCollection<Person> PeopleList
        {
            get { return _peopleList; }
            set
            {
                _peopleList = value;
                RaisePropertyChanged("PeopleList");
            }
        }
 public List<Person> SelectedPersonList
        {
            get { return _selectedPersonList; }
            set
            {
                if (_selectedPersonList != value)
                {
                    RaisePropertyChanged("SelectedPersonList");
                }
            }
        }
 private void DeletePerson()
        {
            // to be able to delete more than one person object
            foreach (Person person in SelectedPersonList)
            {
                PeopleList.Remove(person);
            }
            MessageBox.Show(""+SelectedPersonList.Count); // it is only a checking
            SelectedPersonList.Clear(); // it is a temp list, so it has to be cleared after the button push
        }
 public void GetSelectedPerson(DataGrid datagrid)
        {

            IList selectedItems = datagrid.SelectedItems;
            foreach (Person item in selectedItems)
            {
                SelectedPersonList.Add(item);
            }
        }

我的视图(xmal):

<UserControl x:Class="DataBase.Views.PersonView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DataBase.Views"
             xmlns:viewModel="clr-namespace:DataBase.ViewModels" d:DataContext="{d:DesignInstance Type=viewModel:PersonViewModel}"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel Orientation="Vertical" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Background="AliceBlue">
            <TextBlock Text="First Name:"/>
            <TextBox x:Name="firstNameTxtBox" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Last Name:"/>
            <TextBox x:Name="lastNameTxtBox" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/>
            <TextBlock Text="Age:"/>
            <TextBox x:Name="ageTxtBox" Text="{Binding Age}"/>
            <TextBlock Text="{Binding FullName}"/>
            <Button Content="Add" IsEnabled="{Binding CanAddPerson}" Command="{Binding AddCommand}"/>
            <Button Content="Delete" Command="{Binding DeleteCommand}" Click="Delete_Click"/>
            <DataGrid x:Name="datagridPeopleList" ItemsSource="{Binding PeopleList}" AutoGenerateColumns="True" SelectedItem="{Binding SelectedPerson}" SelectionMode="Extended" SelectionUnit="FullRow"/>
            <!--<ListView Height="50" ItemsSource="{Binding PeopleList}" SelectedItem="{Binding SelectedPerson}" Margin="10">
            </ListView>-->
        </StackPanel>
    </Grid>
</UserControl>

我的视图(.cs):

 public partial class PersonView : UserControl
    {
        public PersonViewModel pvm;
        public PersonView()
        {
            pvm = new PersonViewModel();
            InitializeComponent();
            DataContext = pvm;
        }

        private void Delete_Click(object sender, RoutedEventArgs e)
        {
            pvm.GetSelectedPerson(datagridPeopleList);
        }
    }

希望这对你有用,而且不是世界上最糟糕(不太优雅)的解决方案:D


0
我正在开发的项目使用MVVM Light,我发现这篇博客文章提供了最简单的解决方案。我在此重复解决方案:
视图模型:
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
...

public class SomeVm : ViewModelBase {

    public SomeVm() {
        SelectItemsCommand = new RelayCommand<IList>((items) => {
            Selected.Clear();
            foreach (var item in items) Selected.Add((SomeClass)item);
        });

        ViewCommand = new RelayCommand(() => {
            foreach (var selected in Selected) {
                // todo do something with selected items
            }
        });
    }

    public List<SomeClass> Selected { get; set; }
    public RelayCommand<IList> SelectionChangedCommand { get; set; }
    public RelayCommand ViewCommand { get; set; }
}

XAML:

<Window
    ...
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:command="http://www.galasoft.ch/mvvmlight"
    ...
    <DataGrid
        Name="SomeGrid"
        ...
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <command:EventToCommand
                    Command="{Binding SelectionChangedCommand}"
                    CommandParameter="{Binding SelectedItems, ElementName=SomeGrid}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
        ...
        <DataGrid.ContextMenu>
            <ContextMenu>
                <MenuItem Header="View" Command="{Binding ViewCommand}" />
            </ContextMenu>
        </DataGrid.ContextMenu>
        ...

你的代码很令人困惑,因为在虚拟机中,它的命令被称为SelectionChangedCommand,但在声明时却被称为SelectItemsCommand。 - Joren Vandamme

0

WPF DataGrid可以实现这一点。 只需将DataGrid.Rows.SelectionMode和DataGrid.Rows.SelectionUnit分别设置为“Extended”和“CellOrRowHeader”。如我所示的图像中所示,这可以在Blend中完成。这将允许用户选择每个单元格、整行等,使用shift或ctrl键继续选择,直到满意为止。 enter image description here


3
问题在于在MVVM项目中,您将无法访问所选数据。 - Steffen Winkler

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