WPF 数据网格多选

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31126345/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:14:03  来源:igfitidea点击:

WPF DataGrid MultiSelect

c#wpfmvvmdatagrid

提问by Domonic

I have read several posts on this topic but many are from a previous versions of VS or framework. What I am trying to do is selected multiple rows from a dataGrid and return those rows into a bound observable collection.

我已经阅读了几篇关于这个主题的文章,但很多都来自 VS 或框架的早期版本。我想要做的是从 dataGrid 中选择多行并将这些行返回到绑定的可观察集合中。

I have tried creating a property(of type) and adding it to an observable collection and it works with single records but the code never fires with multiple records.

我曾尝试创建一个属性(类型)并将其添加到一个可观察的集合中,它适用于单个记录,但代码永远不会触发多个记录。

Is there a clean way to do this in VS2013 using an MVVM patern?

在 VS2013 中使用 MVVM 模式有没有一种干净的方法来做到这一点?

Any thoughts would be appreciated.

任何想法将不胜感激。

<DataGrid x:Name="MainDataGrid" Height="390" Width="720" 
                  VerticalAlignment="Center" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"  
                  ItemsSource="{Binding Path=DisplayInDataGrid}"
                  SelectedItem="{Binding Path=DataGridItemSelected}"
                  SelectionMode="Extended"

private ObservableCollection<ScannedItem> _dataGridItemsSelected;
    public ObservableCollection<ScannedItem> DataGridItemsSelected
    {
        get { return _dataGridItemsSelected; }
        set 
        {
            _dataGridItemsSelected = value;
            OnPropertyChanged("DataGridItemsSelected");

        }
    }


    private ScannedItem _dataGridItemSelected;
    public ScannedItem DataGridItemSelected
    {
        get { return _dataGridItemSelected;}
        set
        {
            _dataGridItemSelected = value;
            OnPropertyChanged("DataGridItemSelected");
            EnableButtons();
            LoadSelectedCollection(DataGridItemSelected); 
        }
    }

    void LoadSelectedCollection(ScannedItem si)
    {

        if (DataGridItemsSelected == null)
        {
            DataGridItemsSelected = new ObservableCollection<ScannedItem>();
        }

        DataGridItemsSelected.Add(si);

    }

回答by Angel G

Create a command that fires on the DataGrid's SelectionChangedevent, passing in the DataGrid's SelectedItems.

创建一个在DataGrid'sSelectionChanged事件上触发的命令,传入DataGrid's SelectedItems

In your ViewModel, have a List of selected objects.

在您的 ViewModel 中,有一个选定对象的列表。

Your SelectionChangedCommandexecution method would then update that collection of selected objects.

SelectionChangedCommand然后,您的执行方法将更新所选对象的集合。

For example:

例如:

In my XAML:

在我的 XAML 中:

<DataGrid ItemsSource="{Binding Datasets, NotifyOnTargetUpdated=True}" Name="dsDatagrid" SelectionMode="Extended" MouseDoubleClick="ViewDataset">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <cmd:EventToCommand Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=dsDatagrid, Path=SelectedItems}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</DataGrid>

In my ViewModel:

在我的视图模型中:

private List<ObservableDataset> selectedDatasets;

private List<ObservableDataset> selectedDatasets;

private void SelectionChangedExecuted(object datasets)
{
    this.selectedDatasets = new List<ObservableDataset>((datasets as IList).Cast<ObservableDataset>());
}

EDIT: I'm using MVVMLight.

编辑:我正在使用 MVVMLight。

回答by Stipo

I have implemented a two-way data binding for MultiSelector.SelectedItemsproperty using attached behavior pattern.

我已经使用附加的行为模式为MultiSelector.SelectedItems属性实现了双向数据绑定。

The following image shows how it works:

下图显示了它的工作原理:

MultiSelector.SelectedItems two-way data binding

MultiSelector.SelectedItems 双向数据绑定

There are two DataGrids bound to the same model and they share selected items. Left DataGrid is active so selected items are blue and right DataGrid is inactive so selected items are gray.

有两个 DataGrid 绑定到同一个模型,它们共享选定的项目。左侧 DataGrid 处于活动状态,因此所选项目为蓝色,右侧 DataGrid 处于非活动状态,因此所选项目为灰色。

Following is the sample code how to use it:

以下是如何使用它的示例代码:

MainWindow.xaml

主窗口.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:MainWindowModel/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <DataGrid ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
        <DataGrid Grid.Column="1" ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
        <StackPanel Grid.Row="1" Grid.ColumnSpan="2">
            <Button DockPanel.Dock="Top" Content="Select All" Command="{Binding SelectAllCommand}"/>
            <Button DockPanel.Dock="Top" Content="Unselect All" Command="{Binding UnselectAllCommand}"/>
            <Button DockPanel.Dock="Top" Content="Select Next Range" Command="{Binding SelectNextRangeCommand}"/>
        </StackPanel>
    </Grid>
</Window>

Model.cs

模型.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;

namespace WpfApplication
{
    abstract class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
                handler(this, e);
        }

        protected void Set<T>(ref T field, T value, string propertyName)
        {
            if (!EqualityComparer<T>.Default.Equals(field, value))
            {
                field = value;
                this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    sealed class DelegateCommand : ICommand
    {
        private readonly Action action;

        public DelegateCommand(Action action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            this.action = action;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute()
        {
            this.action();
        }

        bool ICommand.CanExecute(object parameter)
        {
            return true;
        }

        void ICommand.Execute(object parameter)
        {
            this.Execute();
        }
    }

    class Person : ObservableObject
    {
        private string name, surname;

        public Person()
        {
        }

        public Person(string name, string surname)
        {
            this.name = name;
            this.surname = surname;
        }

        public string Name
        {
            get { return this.name; }
            set { this.Set(ref this.name, value, "Name"); }
        }

        public string Surname
        {
            get { return this.surname; }
            set { this.Set(ref this.surname, value, "Surname"); }
        }

        public override string ToString()
        {
            return this.name + ' ' + this.surname;
        }
    }

    class MainWindowModel : ObservableObject
    {
        public ObservableCollection<Person> People { get; private set; }

        public SelectedItemCollection<Person> SelectedPeople { get; private set; }

        public DelegateCommand SelectAllCommand { get; private set; }
        public DelegateCommand UnselectAllCommand { get; private set; }
        public DelegateCommand SelectNextRangeCommand { get; private set; }

        public MainWindowModel()
        {
            this.People = new ObservableCollection<Person>(Enumerable.Range(1, 1000).Select(i => new Person("Name " + i, "Surname " + i)));
            this.SelectedPeople = new SelectedItemCollection<Person>();
            for (int i = 0; i < this.People.Count; i += 2)
                this.SelectedPeople.Add(this.People[i]);

            this.SelectAllCommand = new DelegateCommand(() => this.SelectedPeople.Reset(this.People));

            this.UnselectAllCommand = new DelegateCommand(() => this.SelectedPeople.Clear());

            this.SelectNextRangeCommand = new DelegateCommand(() =>
            {
                var index = this.SelectedPeople.Count > 0 ? this.People.IndexOf(this.SelectedPeople[this.SelectedPeople.Count - 1]) + 1 : 0;

                int count = 10;

                this.SelectedPeople.Reset(Enumerable.Range(index, count).Where(i => i < this.People.Count).Select(i => this.People[i]));
            });

            this.SelectedPeople.CollectionChanged += this.OnSelectedPeopleCollectionChanged;
        }

        private void OnSelectedPeopleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Debug.WriteLine("Action = {0}, NewItems.Count = {1}, NewStartingIndex = {2}, OldItems.Count = {3}, OldStartingIndex = {4}, Total.Count = {5}", e.Action, e.NewItems != null ? e.NewItems.Count : 0, e.NewStartingIndex, e.OldItems != null ? e.OldItems.Count : 0, e.OldStartingIndex, this.SelectedPeople.Count);
        }
    }

    class SelectedItemCollection<T> : ObservableCollection<T>
    {
        public void Reset(IEnumerable<T> items)
        {
            int oldCount = this.Count;

            this.Items.Clear();
            foreach (var item in items)
                this.Items.Add(item);

            if (!(oldCount == 0 && this.Count == 0))
            {
                this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

                if (this.Count != oldCount)
                    this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));

                this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
            }
        }
    }
}

Following is the implementation, which unfortunately is not documented. Implementation uses various tricks (via reflection) to reduce the number of changes to underlying collections as much as possible (to suspend excessive collection changed notifications). It is worth noting that if selected items collection in model has Select(IEnumerable)or Select(IEnumerable)method, that method will be used for performing bulk updates (updates which affect more than one item) which offers better performanse if, for example, all items in the DataGrid are selected or un-selected.

以下是实现,不幸的是没有记录。实现使用各种技巧(通过反射)来尽可能地减少对底层集合的更改次数(暂停过多的集合更改通知)。值得注意的是,如果模型中的选定项目集合具有Select(IEnumerable)Select(IEnumerable)方法,则该方法将用于执行批量更新(影响多个项目的更新),如果例如, DataGrid 中的所有项目都被选中或取消选中。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;

namespace WpfApplication
{
    static class MultiSelectorExtension
    {
        public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectorExtension), new PropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));

        private static readonly DependencyProperty SelectedItemsBinderProperty = DependencyProperty.RegisterAttached("SelectedItemsBinder", typeof(SelectedItemsBinder), typeof(MultiSelectorExtension));

        [AttachedPropertyBrowsableForType(typeof(MultiSelector))]
        [DependsOn("ItemsSource")]
        public static IList GetSelectedItems(this MultiSelector multiSelector)
        {
            if (multiSelector == null)
                throw new ArgumentNullException("multiSelector");

            return (IList)multiSelector.GetValue(SelectedItemsProperty);
        }

        public static void SetSelectedItems(this MultiSelector multiSelector, IList selectedItems)
        {
            if (multiSelector == null)
                throw new ArgumentNullException("multiSelector");

            multiSelector.SetValue(SelectedItemsProperty, selectedItems);
        }

        private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var multiSelector = d as MultiSelector;

            if (multiSelector == null)
                return;

            var binder = (SelectedItemsBinder)multiSelector.GetValue(SelectedItemsBinderProperty);

            var selectedItems = e.NewValue as IList;

            if (selectedItems != null)
            {
                if (binder == null)
                    binder = new SelectedItemsBinder(multiSelector);

                binder.SelectedItems = selectedItems;
            }
            else if (binder != null)
                binder.Dispose();
        }

        private sealed class SelectedItemsBinder : IDisposable
        {
            private static readonly IList emptyList = new object[0];

            private static readonly Action<MultiSelector> multiSelectorBeginUpdateSelectedItems, multiSelectorEndUpdateSelectedItems;

            private readonly MultiSelector multiSelector;
            private IList selectedItems;
            private IResetter selectedItemsResetter;

            private bool suspendMultiSelectorUpdate, suspendSelectedItemsUpdate;

            static SelectedItemsBinder()
            {
                GetMultiSelectorBeginEndUpdateSelectedItems(out multiSelectorBeginUpdateSelectedItems, out multiSelectorEndUpdateSelectedItems);
            }

            public SelectedItemsBinder(MultiSelector multiSelector)
            {
                this.multiSelector = multiSelector;
                this.multiSelector.SelectionChanged += this.OnMultiSelectorSelectionChanged;
                this.multiSelector.Unloaded += this.OnMultiSelectorUnloaded;
                this.multiSelector.SetValue(SelectedItemsBinderProperty, this);
            }

            public IList SelectedItems
            {
                get { return this.selectedItems; }
                set
                {
                    this.SetSelectedItemsChangedHandler(false);
                    this.selectedItems = value;
                    this.selectedItemsResetter = GetResetter(this.selectedItems.GetType());
                    this.SetSelectedItemsChangedHandler(true);

                    if (this.multiSelector.IsLoaded)
                        this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    else
                    {
                        RoutedEventHandler multiSelectorLoadedHandler = null;
                        this.multiSelector.Loaded += multiSelectorLoadedHandler = new RoutedEventHandler((sender, e) =>
                        {
                            this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                            this.multiSelector.Loaded -= multiSelectorLoadedHandler;
                        });
                    }
                }
            }

            private int ItemsSourceCount
            {
                get
                {
                    var collection = this.multiSelector.ItemsSource as ICollection;
                    return collection != null ? collection.Count : -1;
                }
            }

            public void Dispose()
            {
                this.multiSelector.ClearValue(SelectedItemsBinderProperty);
                this.multiSelector.Unloaded -= this.OnMultiSelectorUnloaded;
                this.multiSelector.SelectionChanged -= this.OnMultiSelectorSelectionChanged;
                this.SetSelectedItemsChangedHandler(false);
            }

            private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (this.suspendMultiSelectorUpdate || e.Action == NotifyCollectionChangedAction.Move)
                    return;

                this.suspendSelectedItemsUpdate = true;

                if (this.selectedItems.Count == 0)
                    this.multiSelector.UnselectAll();
                else if (this.selectedItems.Count == this.ItemsSourceCount)
                    this.multiSelector.SelectAll();
                else if (e.Action != NotifyCollectionChangedAction.Reset && (e.NewItems == null || e.NewItems.Count <= 1) && (e.OldItems == null || e.OldItems.Count <= 1))
                    UpdateList(this.multiSelector.SelectedItems, e.NewItems ?? emptyList, e.OldItems ?? emptyList);
                else
                {
                    if (multiSelectorBeginUpdateSelectedItems != null)
                    {
                        multiSelectorBeginUpdateSelectedItems(this.multiSelector);
                        this.multiSelector.SelectedItems.Clear();
                        UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
                        multiSelectorEndUpdateSelectedItems(this.multiSelector);
                    }
                    else
                    {
                        this.multiSelector.UnselectAll();
                        UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
                    }
                }

                this.suspendSelectedItemsUpdate = false;
            }

            private void OnMultiSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (this.suspendSelectedItemsUpdate)
                    return;

                this.suspendMultiSelectorUpdate = true;

                if (e.AddedItems.Count <= 1 && e.RemovedItems.Count <= 1)
                    UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
                else
                {
                    if (this.selectedItemsResetter != null)
                        this.selectedItemsResetter.Reset(this.selectedItems, this.multiSelector.SelectedItems.Cast<object>().Where(item => item != CollectionView.NewItemPlaceholder));
                    else
                        UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
                }

                this.suspendMultiSelectorUpdate = false;
            }

            private void OnMultiSelectorUnloaded(object sender, RoutedEventArgs e)
            {
                this.Dispose();
            }

            private void SetSelectedItemsChangedHandler(bool add)
            {
                var notifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
                if (notifyCollectionChanged != null)
                {
                    if (add)
                        notifyCollectionChanged.CollectionChanged += this.OnSelectedItemsCollectionChanged;
                    else
                        notifyCollectionChanged.CollectionChanged -= this.OnSelectedItemsCollectionChanged;
                }
            }

            private static void UpdateList(IList list, IList newItems, IList oldItems)
            {
                int addedCount = 0;

                for (int i = 0; i < oldItems.Count; ++i)
                {
                    var index = list.IndexOf(oldItems[i]);
                    if (index >= 0)
                    {
                        object newItem;
                        if (i < newItems.Count && (newItem = newItems[i]) != CollectionView.NewItemPlaceholder)
                        {
                            list[index] = newItem;
                            ++addedCount;
                        }
                        else
                            list.RemoveAt(index);
                    }
                }

                for (int i = addedCount; i < newItems.Count; ++i)
                {
                    var newItem = newItems[i];
                    if (newItem != CollectionView.NewItemPlaceholder)
                        list.Add(newItem);
                }
            }

            private static void GetMultiSelectorBeginEndUpdateSelectedItems(out Action<MultiSelector> beginUpdateSelectedItems, out Action<MultiSelector> endUpdateSelectedItems)
            {
                try
                {
                    beginUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
                    endUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
                }
                catch
                {
                    beginUpdateSelectedItems = endUpdateSelectedItems = null;
                }
            }

            private static IResetter GetResetter(Type listType)
            {
                try
                {
                    MethodInfo genericReset = null, nonGenericReset = null;
                    Type genericResetItemType = null;
                    foreach (var method in listType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
                    {
                        if (method.Name != "Reset")
                            continue;

                        if (method.ReturnType != typeof(void))
                            continue;

                        var parameters = method.GetParameters();

                        if (parameters.Length != 1)
                            continue;

                        var parameterType = parameters[0].ParameterType;

                        if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                        {
                            genericResetItemType = parameterType.GetGenericArguments()[0];
                            genericReset = method;
                            break;
                        }
                        else if (parameterType == typeof(IEnumerable))
                            nonGenericReset = method;
                    }

                    if (genericReset != null)
                        return (IResetter)Activator.CreateInstance(typeof(GenericResetter<,>).MakeGenericType(genericReset.DeclaringType, genericResetItemType), genericReset);
                    else if (nonGenericReset != null)
                        return (IResetter)Activator.CreateInstance(typeof(NonGenericResetter<>).MakeGenericType(nonGenericReset.DeclaringType), nonGenericReset);
                    else
                        return null;
                }
                catch
                {
                    return null;
                }
            }

            private interface IResetter
            {
                void Reset(IList list, IEnumerable items);
            }

            private sealed class NonGenericResetter<TTarget> : IResetter
            {
                private readonly Action<TTarget, IEnumerable> reset;

                public NonGenericResetter(MethodInfo method)
                {
                    this.reset = (Action<TTarget, IEnumerable>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable>), method);
                }

                public void Reset(IList list, IEnumerable items)
                {
                    this.reset((TTarget)list, items);
                }
            }

            private sealed class GenericResetter<TTarget, T> : IResetter
            {
                private readonly Action<TTarget, IEnumerable<T>> reset;

                public GenericResetter(MethodInfo method)
                {
                    this.reset = (Action<TTarget, IEnumerable<T>>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable<T>>), method);
                }

                public void Reset(IList list, IEnumerable items)
                {
                    this.reset((TTarget)list, items.Cast<T>());
                }
            }
        }
    }
}