简体   繁体   English

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

[英]Select multiple items from a DataGrid in an MVVM WPF project

如何从 MVVM WPF 项目的DataGrid中选择多个项目?

You can simply add a custom dependency property to do this:你可以简单地添加一个自定义依赖属性来做到这一点:

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
}

Now you can use this dataGrid in the XAML:现在您可以在 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>

My ViewModel :我的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));
    }
}

My model:我的型号:

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

And finally, here is the code behind of MainWindow :最后,这是MainWindow背后的代码:

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

I hope this clean MVVM design helps.我希望这个干净的 MVVM 设计有所帮助。

What I would do is create Behaviors using System.Windows.Interactivity .我会做的是使用System.Windows.Interactivity创建Behaviors You would have to reference it manually in your project.您必须在项目中手动引用它。

Given a control which doesn't expose SelectedItems eg, (ListBox, DataGrid)给定一个不公开SelectedItems控件,例如 (ListBox, DataGrid)

You can create a behavior class something like this你可以创建一个这样的行为类

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

And on your XAML I would do the Binding like this where i is xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and behaviors is the namespace of your Behavior class在您的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>

Assuming that your DataContext for the ListBox has the SelectedItems property in the ViewModel then it will automatically update the SelectedItems .假设ListBoxDataContextViewModel具有SelectedItems属性,那么它将自动更新SelectedItems You have encapsulated the event subscribing from the View ie,您已经封装了从View订阅的event ,即,

<ListBox SelectionChanged="ListBox_SelectionChanged"/>

You can change the Behavior class to be of type DataGrid if you want.如果需要,您可以将Behavior类更改为DataGrid类型。

I use this solution in my app:我在我的应用程序中使用此解决方案:

XAML: XAML:

<i:Interaction.Triggers>
     <i:EventTrigger EventName="SelectionChanged">
         <i:InvokeCommandAction Command="{Binding SelectItemsCommand}" CommandParameter="{Binding Path=SelectedItems,ElementName=TestListView}"/>
     </i:EventTrigger>
</i:Interaction.Triggers>

at the top of you xaml file, add this line of code:在您的 xaml 文件的顶部,添加以下代码行:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

SelectedItemsCommand is ICommand type which is written in your viewmodel. SelectedItemsCommand 是 ICommand 类型,它写在您的视图模型中。

Used DLL:使用的DLL:

System.Windows.Interactivity.dll系统.Windows.Interactivity.dll

With the default DataGrid of WPF it is not possible to use a Binding, as it is possible with the SelectedItem -Property, cause the SelectedItems -Property is not a DependencyProperty.对于 WPF 的默认DataGrid ,无法使用绑定,因为SelectedItem -Property 是可能的,因为SelectedItems -Property 不是 DependencyProperty。

One way to to what you want is to register the SelectionChanged -Event of the DataGrid to update the property of your ViewModel, that stores the selected items.实现您想要的一种方法是注册 DataGrid 的SelectionChanged -Event 以更新存储所选项目的 ViewModel 的属性。

The property SelectedItems of the DataGrid is of type IList so you need to cast the items in the list to your specific type. DataGrid 的SelectedItems属性属于 IList 类型,因此您需要将列表中的项目转换为您的特定类型。

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

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

You can maka a reusable generic base class .您可以创建一个可重用的通用基类 This way you can select rows both from code and UI .通过这种方式,您可以从代码和 UI 中选择行。

This is my example class i want to be selectable这是我想要选择的示例类

public class MyClass
{
    public string MyString {get; set;}   
}

Make generic base class for selectable classes.为可选类制作通用基类。 INotifyPropertyChanged makes the UI update when you set IsSelected.当您设置 IsSelected 时,INotifyPropertyChanged 会更新 UI。

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

Create selectable class创建可选择的类

public class MySelectableItem: SelectableItem<MyClass>
{
    public MySelectableItem(MyClass item)
       :base(item)
    {
    }
}

Create property to bind to创建要绑定到的属性

ObservableCollection<MySelectableItem> MyObservableCollection ...

Set propety设置属性

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

Bind to datagrid and add a style on the DataGridRow that binds to the IsSelected propety on the MySelectedItem绑定到数据网格并在绑定到 MySelectedItem 上的 IsSelected 属性的 DataGridRow 上添加样式

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

To get selected rows/items获取选定的行/项目

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

To select rows/items选择行/项目

MyObservableCollection[0].IsSelected = true;

Edit———> It seems like it does not work when EnableRowVirtualization is true编辑——> EnableRowVirtualization为true时好像不行

WPF DataGrid allows for this. WPF DataGrid 允许这样做。 Simply set the DataGrid.Rows.SelectionMode and DataGrid.Rows.SelectionUnit to "Extended" and "CellOrRowHeader" respectively.只需将 DataGrid.Rows.SelectionMode 和 DataGrid.Rows.SelectionUnit 分别设置为“Extended”和“CellOrRowHeader”。 This can be done in Blend, as shown in the image I have included.这可以在 Blend 中完成,如我包含的图像所示。 This will allow user to select each cell, whole rows etc. as many as they like, using either shift or ctrl key to continue selecting.这将允许用户根据需要选择每个单元格、整行等,使用 shift 或 ctrl 键继续选择。在此处输入图片说明

The project I'm working on uses MVVM Light and I found this blog post to be the simplest solution.我正在处理的项目使用MVVM Light ,我发现这篇博文是最简单的解决方案。 I'll repeat the solution here:我将在这里重复解决方案:

View Model:查看型号:

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

My solution is almost a same like Sandesh.我的解决方案几乎与 Sandesh 相同。 On the other hand, I did not use CustomDataGrid to solve this problem.另一方面,我没有使用 CustomDataGrid 来解决这个问题。 Instead of that I used a plus button click event with the proper function in a viewmodel.相反,我在视图模型中使用了具有适当功能的加号按钮单击事件。 In my code, the main point was that to be able to delete more than one person object from the Datagrid wich was bind to the PeopleList property (ObservableCollection)在我的代码中,要点是能够从绑定到 PeopleList 属性(ObservableCollection)的 Datagrid 中删除多个人物对象

This is my Model:这是我的模型:

 public class Person
    {
        public Person()
        {

        }

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

    }
}

This is my ViewModel (only thoose parts, which are necessary) :这是我的 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);
            }
        }

My View (xmal):我的观点(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>

My View (.cs):我的观点(.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);
        }
    }

I hope that it is useful and not the worst (non elegant) solution in the world :D我希望它是有用的,而不是世界上最糟糕(不优雅)的解决方案:D

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM