【问题标题】:Select multiple items from a DataGrid in an MVVM WPF project从 MVVM WPF 项目中的 DataGrid 中选择多个项目
【发布时间】:2014-05-17 02:24:37
【问题描述】:

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

【问题讨论】:

    标签: c# wpf xaml mvvm datagrid


    【解决方案1】:

    您可以简单地添加一个自定义依赖属性来做到这一点:

    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 设计有所帮助。

    【讨论】:

    • 我尝试了您的代码,它在选择多行时工作正常,但是当我实现删除功能时,它会导致异常。你能看看这个链接stackoverflow.com/questions/29675086/…
    • 看来你得到了答案:)
    • 如果您想检查新选择的项目列表是否等于您的 TestSelected 设置器中的前一个,这将不起作用。原因是 CustomDataGrid_SelectionChanged 中的 SelectedItemsList 和 SelectedItemseason 都包含对列表的相同引用。所以他们永远是平等的。
    • this.SelectedItemsList = this.SelectedItems; 对我不起作用,因为 SelectedItemsList 始终设置为 null。但是,将代码更改为foreach (var item in this.SelectedItems) { this.SelectedItemsList.Add(item); } 就成功了。请注意,这需要您提前致电this.SelectedItemsList.Clear();,因此SelectedItemsList 的项目不会重复。
    • 为什么你有 SelectedItemsList 的双向绑定?我认为您的代码无法处理从其源 (VM) 更改 SelectedItems 属性。
    【解决方案2】:

    我要做的是使用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"behaviorsBehavior 类的命名空间

    <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 这里的
      @MegaMind 我想强调的是,当您使用行为时,您不需要 SelectionChanged 事件。
    • @III 我已经尝试过您的解决方案,但在视图模型中,所选项目始终保持为空。我已经编译了一个描述您的代码的简单解决方案。如果你能看看那个。 dropbox.com/s/lh4zrhnatpchqzi/MultiSelectedDataGrid.zip?dl=0
    • @III - 是的,我很傻,我引用了相同的答案。我现在无法编辑我的坏评论,所以我删除了它 - 这是对我有用的评论:stackoverflow.com/a/8088926。正如我在旧评论(现已删除)中所说,您的答案对我不起作用,SelectedItems 始终为空,就像 MegaMind 一样。另一个答案和你的几乎一样,对我有用。
    • 只要SelectedItemsIEnumerable 类型,我也得到null。但是一旦类型更改为IList,数据就来了。不幸的是,绑定并不是真正的双向。解决方案是这个答案和这个blog entry的组合
    【解决方案3】:

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

    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 文件的顶部,添加以下代码:

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

    SelectedItemsCommand 是写在您的视图模型中的 ICommand 类型。

    使用的 DLL:

    System.Windows.Interactivity.dll

    【讨论】:

      【解决方案4】:

      使用 WPF 的默认 DataGrid 无法使用绑定,因为使用 SelectedItem-Property 是可能的,因为 SelectedItems-Property 不是 DependencyProperty。

      您想要的一种方法是注册 DataGrid 的 SelectionChanged-Event 以更新存储所选项目的 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>
      

      【讨论】:

      • 感谢您的回答,但我正在寻找基于 MVVM 且不应涉及代码背后的东西
      • 我很欣赏这个答案。尽管它不是 MVVM,但它是第一个提到 为什么我无法访问 XAML 中的 SelectedItems 属性。
      • 而且它根本不会破坏您的 MVVM 模型。您的 VM 中仍然有一个纯 SelectedMyObjects 属性,它对 View 或其设置方式一无所知。仅仅在 View 中有一些代码并不意味着它不是纯粹的 MVVM。
      【解决方案5】:

      可以在Model中添加“IsSelected”属性,并在行中添加checkBox。

      【讨论】:

        【解决方案6】:

        您可以创建一个可重用的通用基类。这样您就可以从代码和 UI 中选择行

        这是我希望可选择的示例类

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

        为可选类创建通用基类。当您设置 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;
        }
        

        创建可选择的类

        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;
        

        编辑——> EnableRowVirtualization 为 true 时似乎不起作用

        【讨论】:

        • 不幸的是,当当前不可见的行(因为它们被滚动出视图)被选中或取消选中时,这对我来说并不可靠。
        • EnableRowVirtualization 为真时好像不行
        【解决方案7】:

        WPF DataGrid 允许这样做。 只需将 DataGrid.Rows.SelectionMode 和 DataGrid.Rows.SelectionUnit 分别设置为“Extended”和“CellOrRowHeader”。这可以在 Blend 中完成,如我所包含的图像所示。这将允许用户尽可能多地选择每个单元格、整行等,使用 shift 或 ctrl 键继续选择。

        【讨论】:

        • 问题是您将无法访问 MVVM 项目中的选定数据。
        【解决方案8】:

        我正在进行的项目使用MVVM Light,我发现this blog post 是最简单的解决方案。我将在这里重复解决方案:

        查看模型:

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

        【讨论】:

          【解决方案9】:

          我的解决方案与 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

          【讨论】:

            猜你喜欢
            相关资源
            最近更新 更多
            热门标签