c# - WPF 数据网格多选

标签 c# wpf mvvm datagrid

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

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

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

如有任何想法,我们将不胜感激。

<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 实现了双向数据绑定(bind)使用附加行为模式的属性。

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

MultiSelector.SelectedItems two-way data binding

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

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

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>

模型.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 中的所有项目都被选中或未被选中。

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

关于c# - WPF 数据网格多选,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31126345/

相关文章:

wpf - MvvmLight 良好实践 : Show a Form by a ViewModel

swift - RxSwift 和 MVVM : observable not executing without binding

c# - Azure 计算模拟器无法启动。代理因错误而崩溃(连续)

c# - 动态命令行参数和预加载任务,同时保持调试

C# 为 const 成员分配函数

wpf - 在屏幕之间切换 WPF

wpf - FrameworkElement.Loaded 事件如何与虚拟化配合使用?

c# - 将 UserControl 命令绑定(bind)到 MainWindow WPF/MVVM

c# - ItextSharp 将图像缩放/调整大小为 PDF

c# - 如何在 Web API Controller 中接收字节数组和 json