WPF DataGrid多选 [英] WPF DataGrid MultiSelect

查看:95
本文介绍了WPF DataGrid多选的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

我尝试创建一个属性(类型)并添加

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.

在VS2013中,有没有一种干净的方法可以使用MVVM模式执行此操作?

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

任何想法都会受到赞赏。

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

    }


推荐答案

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

下图显示了它的工作方式:

The following image shows how it works:

有两个绑定到同一模型的DataGrid,它们共享选定的项目。

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.

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

Following is the sample code how to use it:

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

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[]"));
            }
        }
    }
}

是实现,遗憾的是没有实现。实现使用各种技巧(通过反射)来尽可能减少对基础集合的更改数量(以暂停过多的集合更改通知)。
值得注意的是,如果模型中的选定项目集合具有 Select(IEnumerable) Select(IEnumerable)方法,该方法将用于执行批量更新(影响多个项目的更新),例如,如果选中或未选中DataGrid中的所有项目,则性能会更好。

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.

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

这篇关于WPF DataGrid多选的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆