Laden...

DataGrid wirft InvalidOperationException beim Scrollen

Erstellt von dredav vor 11 Jahren Letzter Beitrag vor 11 Jahren 5.382 Views
D
dredav Themenstarter:in
34 Beiträge seit 2012
vor 11 Jahren
DataGrid wirft InvalidOperationException beim Scrollen

Hallo,

ich möchte den Inhalt einer Datei in einem DataGrid darstellen (Die Date beinhaltet mehr als 200000 Zeilen).

Die Anzeige der Zeilen geht schnell und sie auch gut aus.
Aber wenn ich mit der Scrollbar nach unten Scrolle, bekomme ich folgende Exception:

System.InvalidOperationException:  
{"An ItemsControl is inconsistent with its items source.\n  See the inner exception for more information."}

InnerException:

Information for developers (use Text Visualizer to read this):  
This exception was thrown because the generator for control 'System.Windows.Controls.DataGrid Items.Count:0' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection.  The following differences were detected:  
  Accumulated count 0 is different from actual count 200000.  [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset).]  
  
One or more of the following sources may have raised the wrong events:  
     System.Windows.Controls.ItemContainerGenerator  
      System.Windows.Controls.ItemCollection  
       System.Windows.Data.ListCollectionView  
        System.Collections.Generic.List`1[[WpfApplication3.Entry, WpfApplication3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]  
(The starred sources are considered more likely to be the cause of the problem.)  
  
The most common causes are (a) changing the collection or its Count without raising a corresponding event, and (b) raising an event with an incorrect index or item parameter.  
  
The exception's stack trace describes how the inconsistencies were detected, not how they occurred.  To get a more timely exception, set the attached property 'PresentationTraceSources.TraceLevel' on the generator to value 'High' and rerun the scenario.  One way to do this is to run a command similar to the following:  
   System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator, System.Diagnostics.PresentationTraceLevel.High)  
from the Immediate window.  This causes the detection logic to run after every CollectionChanged event, so it will slow down the application.

Die Exception sagt ja, dass das Grid Events erhalten hat, welche nicht mit dem Aktuellen Status der Collection übereinstimmen, was heißt das?

Das ist mein Code:

MainWindow.xaml

    <Window x:Class="WpfApplication3.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication3="clr-namespace:WpfApplication3"
            Title="MainWindow" Height="350" Width="525">
        <Grid Name="Test">
            <WpfApplication3:Viewer x:Name="LogUC" />
        </Grid>
    </Window>

MainWindow.xaml.cs

	public partial class MainWindow
	{
		public MainWindow()
		{
			InitializeComponent();
			Test.DataContext = this;

			LogUC.Loaded += LogUcOnLoaded;
		}

		private void LogUcOnLoaded(object sender, RoutedEventArgs routedEventArgs)
		{
			LogUC.Test();
		}
	}

Viewer.xaml

    <UserControl x:Class="WpfApplication3.Viewer"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Height="300" Width="300">
        <Grid Name="Container">
            <DataGrid ItemsSource="{Binding Path=EntryCollection, Mode=OneTime}"
                      AutoGenerateColumns="False"
                      CanUserResizeColumns="True"
                      CanUserResizeRows="True"
                      CanUserAddRows="False"
                      CanUserDeleteRows="False"
                      IsReadOnly="True">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding ErrorCode}" Header="" />
                    <DataGridTextColumn Binding="{Binding Time}" Header="Time" />
                    <DataGridTextColumn Binding="{Binding Content}" Header="Content" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </UserControl>

Viewer.xaml.cs

   public partial class Viewer : INotifyPropertyChanged
    {
        public Viewer()
        {
            EntryCollection = new List<Entry>();
            InitializeComponent();
            Container.DataContext = this;
        }

        public List<Entry> EntryCollection { get; set; }

        internal void Test()
        {
            List<Entry> test = new List<Entry>();

            for (int i = 0; i < 200000; i++)
            {
                Entry entry = new Entry(){
                    ErrorCode = 0,
                    Time = DateTime.Now.ToString(),
                    Content = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
                };
                test.Add(entry);
            }

            EntryCollection.AddRange(test);
            OnPropertyChanged("EntryCollection");
        }

        #region Implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion Implementation of INotifyPropertyChanged
    }

Entry.cs

    public class Entry
    {
        public int ErrorCode { get; set; }

        public string Time { get; set; }

        public string Content { get; set; }
    }

Was bzw. Wo ist das Problem?

1.378 Beiträge seit 2006
vor 11 Jahren

Hallo dredav,

ich glaub zwar nicht, dass es daran liegt aber probier mal deine List<Entry> durch eine ObservableCollection<Entry> auszuwechseln und lass dann bei der Methode "Test" das OnPropertyChanged("EntryCollection") weg <- das erübrigt sich mit der anderen Collection.

Hab vergessen, dass es ja nur beim Scrollen auftritt.

Lg, XXX

D
dredav Themenstarter:in
34 Beiträge seit 2012
vor 11 Jahren

Hallo xxxprod,

mit der ObservableCollection geht es. (ich bin verwundert....)

Nun habe ich die ObservableCollection ganz am Anfang schon verwendet mich jedoch gegen sie entschieden, da das Laden der Datei ca 3 Sekunden länger Dauert als mit der List. Ich gehe davon aus das es daran liegt das die ObservableCollection bei jedem Add das Grid aktualisiert (stimmt das)?

Grüße
David

1.378 Beiträge seit 2006
vor 11 Jahren

probier mal folgendes:


var view = CollectionViewSource.GetDefaultView(collection);
using(view.DeferRefresh())
{
   //add items here.
}

Lg, XXX

D
dredav Themenstarter:in
34 Beiträge seit 2012
vor 11 Jahren

probier mal folgendes:

  
var view = CollectionViewSource.GetDefaultView(collection);  
using(view.DeferRefresh())  
{  
   //add items here.  
}  
  

Lg, XXX

Meinst du so:

        public ObservableCollection<Entry> EntryCollection { get; set; }

        internal void Test()
        {
            var view = CollectionViewSource.GetDefaultView(EntryCollection);
            using (view.DeferRefresh())
            {
                for (int i = 0; i < 200000; i++)
                {
                    Entry entry = new Entry()
                                      {
                                          ErrorCode = 0,
                                          Time = DateTime.Now.ToString(),
                                          Content = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
                                      };
                    EntryCollection.Add(entry);
                }
            }
        }

Wenn ja, dann bekomme ich folgende Exception:

Cannot change or check the contents or Current position of CollectionView while Refresh is being deferred.

1.378 Beiträge seit 2006
vor 11 Jahren

Ok, ich sehe gerade, dass dieses DeferRefresh in der ObservableCollection nur für Änderungen am Sort oder Filter gedacht sind. Nicht aber für Adds und Removes. Bei mir funktioniert es deshalb, weil ich meine "eigene" ObservableCollection implementiert habe in der ich während des Updates keine OnCollectionChanged Events mehr feuere und somit die View sich solange auch nicht mehr aktualisiert.

Hier meine Implementierung:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows.Data;

namespace XXXprod
{
    public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;


        public ObservableCollection()
        {
            DefaultView = CollectionViewSource.GetDefaultView(this);
        }

        public ObservableCollection(IEnumerable<T> items)
            : this()
        {
            AddRange(items);
        }

        public ICollectionView DefaultView { get; private set; }

        private void OnCollectionCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (_deferringRefresh)
                return;

            NotifyCollectionChangedEventHandler handler = CollectionChanged;
            if (handler != null)
                handler(this, e);
        }

        private bool _deferringRefresh;
        public IDisposable UpdateCollection()
        {
            _deferringRefresh = true;
            return new DelegateDisposable(DefaultView.DeferRefresh(), () =>
            {
                _deferringRefresh = false;
                RefreshCollectionView();
            });
        }

        public void RefreshCollectionView()
        {
            DefaultView.Refresh();
        }


        public void Synchronize(IEnumerable<T> items)
        {
            using (UpdateCollection())
            {
                foreach (var removeResult in this.Except(items).ToArray())
                    Remove(removeResult);

                foreach (var removeResult in items.Except(this).ToArray())
                    Add(removeResult);
            }
        }


        public void AddRange(IEnumerable<T> items)
        {
            foreach (var item in items)
                Add(item);
        }


        protected override void InsertItem(int index, T item)
        {
            base.InsertItem(index, item);
            OnCollectionCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
        }
        protected override void RemoveItem(int index)
        {
            T item = base[index];
            base.RemoveItem(index);
            OnCollectionCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
        }

        protected override void SetItem(int index, T item)
        {
            T oldItem = base[index];
            base.SetItem(index, item);
            OnCollectionCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, item, oldItem, index));
        }

        protected override void ClearItems()
        {
            using (UpdateCollection())
            {
                T[] oldItems = this.ToArray();

                foreach (T item in oldItems)
                    Remove(item);
            }
        }
    }

    public class DelegateDisposable : IDisposable
    {
        private readonly IDisposable _disposable;
        private readonly Action _disposed;

        public DelegateDisposable(IDisposable disposable, Action disposed)
        {
            _disposable = disposable;
            _disposed = disposed;
        }

        public void Dispose()
        {
            if (_disposable != null)
                _disposable.Dispose();
            _disposed();
        }
    }
}

Lg, XXX

5.742 Beiträge seit 2007
vor 11 Jahren

da das Laden der Datei ca 3 Sekunden länger Dauert als mit der List. Ich gehe davon aus das es daran liegt das die ObservableCollection bei jedem Add das Grid aktualisiert (stimmt das)?

Dann erzeuge in der Test-Methode einfach eine neue Collection, fülle diese, und weise sie danach der Property zu; dann findet nur genau eine Aktualisierung statt.

Eine ObservableCollection<> ist definitiv das richtige in diesem Kontext.
Und irgendwelche Workarounds braucht man auch nicht.

D
dredav Themenstarter:in
34 Beiträge seit 2012
vor 11 Jahren

da das Laden der Datei ca 3 Sekunden länger Dauert als mit der List. Ich gehe davon aus das es daran liegt das die ObservableCollection bei jedem Add das Grid aktualisiert (stimmt das)?
Dann erzeuge in der Test-Methode einfach eine neue Collection, fülle diese, und weise sie danach der Property zu; dann findet nur genau eine Aktualisierung statt.

Eine ObservableCollection<> ist definitiv das richtige in diesem Kontext.
Und irgendwelche Workarounds braucht man auch nicht.

hmmpf danke 😉 wieso bin ich da nicht gleich drauf gekommen...