当添加项目后DataGrid刷新自身时,索引ArgumentOutOfRangeException

时间:2019-09-05 12:04:13

标签: wpf datagrid

我正在使用DataGrid来显示自定义集合PersonCollection : List<Person>, INotifyCollectionChanged(只是通过痛苦的示例from a book学习WPF)。如果我将项目添加到最初为空的集合中,以及从包含多个项目的集合中删除项目,该程序也会起作用。在这种情况下,DataGrid已正确更新。

但是,有一种情况导致ArgumentOutOfRangeException异常:

  • DataGrid绑定到非空集合。
  • 从集合中删除所有项目。
  • 将项目添加到空集合中。

在这种情况下,我会遇到此异常:

System.ArgumentOutOfRangeException
  HResult=0x80131502
  Message=Specified argument was out of the range of valid values.
Parameter name: index
  Source=PresentationFramework
  StackTrace:
   at System.Windows.Controls.ItemCollection.GetItemAt(Int32 index)
   at System.Windows.Controls.VirtualizedCellInfoCollection.Contains(DataGridCell cell)
   at System.Windows.Controls.DataGridCell.PrepareCell(Object item, DataGridRow ownerRow, Int32 index)
   at System.Windows.Controls.Primitives.DataGridCellsPresenter.PrepareContainerForItemOverride(DependencyObject element, Object item)
   at System.Windows.Controls.ItemsControl.MS.Internal.Controls.IGeneratorHost.PrepareItemContainer(DependencyObject container, Object item)
   at System.Windows.Controls.ItemContainerGenerator.System.Windows.Controls.Primitives.IItemContainerGenerator.PrepareItemContainer(DependencyObject container)
   at System.Windows.Controls.DataGridCellsPanel.InsertContainer(Int32 childIndex, UIElement container, Boolean isRecycled)
   at System.Windows.Controls.DataGridCellsPanel.AddContainerFromGenerator(Int32 childIndex, UIElement child, Boolean newlyRealized)
   at System.Windows.Controls.DataGridCellsPanel.GenerateChild(IItemContainerGenerator generator, Size constraint, DataGridColumn column, Int32& childIndex, Size& childSize)
   at System.Windows.Controls.DataGridCellsPanel.GenerateChildren(IItemContainerGenerator generator, Int32 startIndex, Int32 endIndex, Size constraint)
   at System.Windows.Controls.DataGridCellsPanel.GenerateAndMeasureChildrenForRealizedColumns(Size constraint)
   at System.Windows.Controls.DataGridCellsPanel.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
   at System.Windows.Controls.ItemsPresenter.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Control.MeasureOverride(Size constraint)
   at System.Windows.Controls.Primitives.DataGridCellsPresenter.MeasureOverride(Size availableSize)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
   at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged)
   at System.Windows.Controls.Grid.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Control.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureChild(IItemContainerGenerator& generator, IContainItemStorage& itemStorageProvider, IContainItemStorage& parentItemStorageProvider, Object& parentItem, Boolean& hasUniformOrAverageContainerSizeBeenSet, Double& computedUniformOrAverageContainerSize, Double& computedUniformOrAverageContainerPixelSize, Boolean& computedAreContainersUniformlySized, Boolean& hasAnyContainerSpanChanged, IList& items, Object& item, IList& children, Int32& childIndex, Boolean& visualOrderChanged, Boolean& isHorizontal, Size& childConstraint, Rect& viewport, VirtualizationCacheLength& cacheSize, VirtualizationCacheLengthUnit& cacheUnit, Boolean& foundFirstItemInViewport, Double& firstItemInViewportOffset, Size& stackPixelSize, Size& stackPixelSizeInViewport, Size& stackPixelSizeInCacheBeforeViewport, Size& stackPixelSizeInCacheAfterViewport, Size& stackLogicalSize, Size& stackLogicalSizeInViewport, Size& stackLogicalSizeInCacheBeforeViewport, Size& stackLogicalSizeInCacheAfterViewport, Boolean& mustDisableVirtualization, Boolean isBeforeFirstItem, Boolean isAfterFirstItem, Boolean isAfterLastItem, Boolean skipActualMeasure, Boolean skipGeneration, Boolean& hasBringIntoViewContainerBeenMeasured, Boolean& hasVirtualizingChildren)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverrideImpl(Size constraint, Nullable`1& lastPageSafeOffset, List`1& previouslyMeasuredOffsets, Nullable`1& lastPagePixelSize, Boolean remeasure)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(Size constraint)
   at System.Windows.Controls.Primitives.DataGridRowsPresenter.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
   at System.Windows.Media.MediaContext.InvokeOnRenderCallback.DoWork()
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(Object obj)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at BindingCollection.App.Main()

我无法理解此异常的原因,该异常是在将新项添加到先前清空的集合后DataGrid重新绘制自身时发生的,并且在其中看不到index的无效值VisualStudio调试器。

XAML:

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

<StackPanel>
    <DataGrid x:Name="grid"
              Width="500" Margin="10" HorizontalAlignment="Center"
              AutoGenerateColumns="True"
              ItemsSource="{Binding Collection}"/>

    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <Button Margin="10" Content="Add" Click="AddClick"/>
        <Button Margin="10" Content="Delete" Click="DeleteClick"/>
    </StackPanel>
</StackPanel>
</Window>

C#:

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace WpfApp1 {
    public partial class MainWindow : Window {
        ViewModel viewModel = new ViewModel ();

        public MainWindow () {
            InitializeComponent ();
            DataContext = viewModel;
        }

        private void AddClick (object sender, RoutedEventArgs e) {
            viewModel.AddNew ();
        }

        private void DeleteClick (object sender, RoutedEventArgs e) {
            int index = grid.SelectedIndex;
            if (index < 0) {
                return;
            }
            viewModel.RemoveAt (index);
        }
    }

    public class Person : INotifyPropertyChanged {
        string firstName;
        public event PropertyChangedEventHandler PropertyChanged;

        public string FirstName {
            get => firstName;
            set { firstName = value; OnPropertyChanged (); }
        }

        public Person (string firstName) {
            FirstName = firstName;
        }

        protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
        }
    }

    public class PersonCollection : List<Person>, INotifyCollectionChanged {
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public new void Add (Person person) {
            base.Add (person);
            NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs (
                NotifyCollectionChangedAction.Add, person);
            OnCollectionChanged (e);
        }

        public new void RemoveAt (int index) {
            Person person = base[index];
            base.RemoveAt (index);
            NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs (
                NotifyCollectionChangedAction.Remove, person);
            OnCollectionChanged (e);
        }

        protected virtual void OnCollectionChanged (NotifyCollectionChangedEventArgs e) {
            CollectionChanged?.Invoke (this, e);
        }
    }

    class ViewModel : INotifyPropertyChanged {
        private PersonCollection collection;
        public event PropertyChangedEventHandler PropertyChanged;

        public PersonCollection Collection {
            get => collection;
            set {
                collection = value;
                OnPropertyChanged ();
            }
        }

        public ViewModel () {
            Collection = new PersonCollection { new Person ("Joe") };
        }

        public void AddNew () {
            Collection.Add (new Person ("Default name"));
        }

        public void RemoveAt (int index) {
            Collection.RemoveAt (index);
        }

        protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null) {
            PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
        }
    }
}

2 个答案:

答案 0 :(得分:1)

删除项目时,还应将其索引传递给NotifyCollectionChangedEventArgs构造函数:

public new void RemoveAt(int index)
{
    var element = this[index];
    base.RemoveAt(index);

    CollectionChanged?.Invoke(this,
        new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Remove, element, index));
}

虽然在特殊情况下并非绝对必要,但您当然也可以传递添加元素的索引:

public new void Add(T element)
{
    base.Add(element);

    CollectionChanged?.Invoke(this,
        new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add, element, Count - 1));
}

答案 1 :(得分:1)

如果要模仿ObservableCollection<T>的行为,则应实现INotifyPropertyChanged接口,并为Count和索引器属性引发更改通知。您还应该在NotifyCollectionChangedEventArgs中包括已添加或已删除项目的索引,以使数据绑定的ItemsControl能够按预期工作:

public class PersonCollection : List<Person>, INotifyCollectionChanged, INotifyPropertyChanged
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    public event PropertyChangedEventHandler PropertyChanged;

    public new void Add(Person person)
    {
        int index = Count;
        base.Add(person);
        NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(
            NotifyCollectionChangedAction.Add, person, Count);
        OnPropertyChanged("Count");
        OnPropertyChanged("Item[]");
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, person, index));
    }

    public new void RemoveAt(int index)
    {
        Person person = base[index];
        base.RemoveAt(index);
        OnPropertyChanged("Count");
        OnPropertyChanged("Item[]");
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, person, index));
    }

    protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        CollectionChanged?.Invoke(this, e);
    }

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}