I have a performance issue with datagrid - rendering takes too long and freezes my app until it's all rendered.
QuestionTester.View.QuestionView:
Expand|Select|Wrap|Line Numbers
- <UserControl x:Class="QuestionTester.View.QuestionView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:local="clr-namespace:QuestionTester.View"
- mc:Ignorable="d"
- xmlns:Converters="clr-namespace:QuestionTester.Converters"
- d:DesignHeight="450" d:DesignWidth="800">
- <UserControl.Resources>
- <Converters:FromDateConverter x:Key="FromDateConv"/>
- <Converters:ToDateConverter x:Key="ToDateConv"/>
- <Converters:ToTimeConverter x:Key="ToTimeConv"/>
- </UserControl.Resources>
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="20"/>
- <RowDefinition Height="*"/>
- </Grid.RowDefinitions>
- <TextBlock Grid.Row="0" Text="{Binding RecordsCount}"/>
- <DataGrid
- Grid.Row="1"
- ItemsSource="{Binding Models}"
- IsReadOnly="True"
- AutoGenerateColumns="False"
- SnapsToDevicePixels="False"
- VirtualizingStackPanel.IsVirtualizing="True"
- VirtualizingStackPanel.VirtualizationMode="Recycling"
- EnableRowVirtualization="True"
- MaxWidth="2560"
- MaxHeight="1600">
- <DataGrid.Columns>
- <DataGridTemplateColumn Header="CreationTime" ToolTipService.ToolTip="CreationTime">
- <DataGridTemplateColumn.CellTemplate>
- <DataTemplate>
- <StackPanel Margin="4,0,0,0" FlowDirection="LeftToRight" VerticalAlignment="Center">
- <ToolTipService.ToolTip>
- <TextBlock Text="{Binding Path=CreationTime, Converter={StaticResource ToTimeConv}}"/>
- </ToolTipService.ToolTip>
- <TextBlock HorizontalAlignment="Left" Text="{Binding Path=CreationTime, Converter={StaticResource ToTimeConv}}"/>
- </StackPanel>
- </DataTemplate>
- </DataGridTemplateColumn.CellTemplate>
- </DataGridTemplateColumn>
- <DataGridTemplateColumn Header="StartDate" ToolTipService.ToolTip="StartDate">
- <DataGridTemplateColumn.CellTemplate>
- <DataTemplate>
- <StackPanel Margin="4,0,0,0" FlowDirection="LeftToRight" VerticalAlignment="Center">
- <ToolTipService.ToolTip>
- <TextBlock Text="{Binding Path=StartDate, Converter={StaticResource FromDateConv}}"/>
- </ToolTipService.ToolTip>
- <TextBlock HorizontalAlignment="Left" Text="{Binding Path=StartDate, Converter={StaticResource FromDateConv}}"/>
- </StackPanel>
- </DataTemplate>
- </DataGridTemplateColumn.CellTemplate>
- </DataGridTemplateColumn>
- <DataGridTemplateColumn Header="EndDate" ToolTipService.ToolTip="EndDate">
- <DataGridTemplateColumn.CellTemplate>
- <DataTemplate>
- <StackPanel Margin="4,0,0,0" FlowDirection="LeftToRight" VerticalAlignment="Center">
- <ToolTipService.ToolTip>
- <TextBlock Text="{Binding Path=EndDate, Converter={StaticResource ToDateConv}}"/>
- </ToolTipService.ToolTip>
- <TextBlock HorizontalAlignment="Left" Text="{Binding Path=EndDate, Converter={StaticResource ToDateConv}}"/>
- </StackPanel>
- </DataTemplate>
- </DataGridTemplateColumn.CellTemplate>
- </DataGridTemplateColumn>
- <DataGridTextColumn Header="Guid" Binding="{Binding Path=Guid}"/>
- <DataGridTextColumn Header="Key" Binding="{Binding Path=Key}"/>
- <DataGridTextColumn Header="Field" Binding="{Binding Path=Field}"/>
- <DataGridTextColumn Header="Field1" Binding="{Binding Path=Field1}"/>
- <DataGridTextColumn Header="Field2" Binding="{Binding Path=Field2}"/>
- </DataGrid.Columns>
- </DataGrid>
- </Grid>
- </UserControl>
Expand|Select|Wrap|Line Numbers
- public partial class QuestionView : UserControl
- {
- QuestionViewModel _vm;
- public QuestionView()
- {
- InitializeComponent();
- _vm = new QuestionViewModel(TaskScheduler.FromCurrentSynchronizationContext());
- DataContext = _vm;
- }
- }
Expand|Select|Wrap|Line Numbers
- public class QuestionModel
- {
- public static Random _rand = new Random();
- public DateTime StartDate { get; set; }
- public DateTime EndDate { get; set; }
- public DateTime CreationTime{ get; set; }
- public Guid Guid { get; set; }
- public string Key { get; set; }
- public int Field { get; set; }
- public long Field1 { get; set; }
- public double Field2 { get; set; }
- public QuestionModel(int num)
- {
- Key = $"key_{num}";
- Field = _rand.Next(1000000);
- Field1 = _rand.Next(1000000);
- Field2 = _rand.Next(1000000);
- Guid = Guid.NewGuid();
- CreationTime = DateTime.Now;
- if (num % 2 == 0)
- {
- StartDate = DateTime.Now.AddDays(-1);
- EndDate = DateTime.Now.AddDays(1);
- }
- if (num % 10 == 0)
- {
- StartDate = DateTime.Now.AddDays(-100);
- EndDate = DateTime.Now.AddDays(100);
- }
- else
- {
- StartDate = DateTime.Now;
- EndDate = DateTime.Now;
- }
- }
- public override string ToString()
- {
- return $"CreationTime:{CreationTime}";
- }
- }
Expand|Select|Wrap|Line Numbers
- public class QuestionViewModel : NotificationBase, IUpdateTotalItems, IDisposable
- {
- const int RAND = 10000000;
- static Random _rand = new Random();
- const int LENGTH = 1000;
- public BulkObservableCollection<QuestionModel> Models { get; }// = new BulkObservableCollection<QuestionModel>();
- TaskScheduler _currentcontext;
- Guid _cancelToken;
- HashSet<Guid> _cancelTokensDict = new HashSet<Guid>();
- DispatcherTimer _dispatcherUpdateTimer;
- Thread _thread;
- public QuestionViewModel(TaskScheduler currentcontext)
- {
- _currentcontext = currentcontext;
- _cancelToken = Guid.NewGuid();
- Models = new BulkObservableCollection<QuestionModel>(_cancelToken, ref _cancelTokensDict, this);
- _dispatcherUpdateTimer = new DispatcherTimer(DispatcherPriority.Background);
- _dispatcherUpdateTimer.Interval = TimeSpan.FromMilliseconds(1000);
- _dispatcherUpdateTimer.Tick += _dispatcherUpdateTimer_Tick;
- _dispatcherUpdateTimer.Start();
- QuestionModel[] array = new QuestionModel[LENGTH];
- for (int i = 0; i < LENGTH; i++)
- {
- int indx = _rand.Next(RAND);
- array[i] = new QuestionModel(indx);
- }
- Models.AddRange(array);
- Models.LoadingFinished();
- _thread = new Thread(() =>
- {
- while (!_cancelTokensDict.Contains(_cancelToken))
- {
- QuestionModel[] arrayThread = new QuestionModel[LENGTH];
- for (int i = 0; i < LENGTH; i++)
- {
- int indx = _rand.Next(RAND);
- arrayThread[i] = new QuestionModel(indx);
- }
- Task.Factory.StartNew(() =>
- {
- try
- {
- for (int i = 0; i < arrayThread.Length; i++)
- {
- Models.Enqueue(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, arrayThread[i], 0));
- }
- // Models.AddRange(arrayThread);
- }
- catch (Exception)
- {
- throw;
- }
- }, CancellationToken.None, TaskCreationOptions.None, _currentcontext);
- Thread.Sleep(1000);
- }
- });
- _thread.IsBackground = true;
- _thread.Start();
- }
- private int Comparison(QuestionModel a, QuestionModel b)
- {
- if (a == null)
- return 1;
- if (b == null)
- return -1;
- var comprand = b.CreationTime.CompareTo(a.CreationTime);
- return comprand;
- }
- private void _dispatcherUpdateTimer_Tick(object sender, EventArgs e)
- {
- if (Models.IsLoading)
- return;
- Models.Refresh();
- }
- int _recordsCound;
- public int RecordsCount
- {
- get => _recordsCound;
- set
- {
- if (_recordsCound != value)
- {
- _recordsCound = value;
- OnPropertyChanged("RecordsCount");
- }
- }
- }
- public void UpdateTotalItems(int count)
- {
- RecordsCount = count;
- }
- bool _isDisposed = false;
- public void Dispose()
- {
- if (!_isDisposed)
- {
- _isDisposed = true;
- lock (_cancelTokensDict)
- {
- _cancelTokensDict.Add(_cancelToken);
- }
- if (_thread != null)
- {
- try
- {
- _thread.Join();
- }
- catch
- {
- }
- _thread = null;
- }
- if (_dispatcherUpdateTimer != null)
- {
- _dispatcherUpdateTimer.Stop();
- _dispatcherUpdateTimer.Tick -= _dispatcherUpdateTimer_Tick;
- _dispatcherUpdateTimer = null;
- }
- }
- }
- }
Expand|Select|Wrap|Line Numbers
- public class ToTimeConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value == null)
- return null;
- DateTime? date = (DateTime)value;
- if (date.HasValue)
- {
- return date.Value.ToString("HH:mm:ss:fff");
- }
- return null;
- }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
- public class ToDateConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value == null)
- return null;
- DateTime? date = (DateTime)value;
- if (date.HasValue)
- {
- if (date.Value.Date == DateTime.Now.Date)
- {
- return "Today";
- }
- else if (date.Value.Date.Date == DateTime.Now.Date.AddDays(1))
- {
- return "Tomorrow";
- }
- return date.Value.Date.ToShortDateString();
- }
- return null;
- }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
- public class FromDateConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value == null)
- return null;
- DateTime? date = (DateTime)value;
- if (date.HasValue)
- {
- if (date.Value.Date == DateTime.Now.Date)
- {
- return "Today";
- }
- else if (date.Value.Date.Date == DateTime.Now.Date.AddDays(-1))
- {
- return "Yesterday";
- }
- return date.Value.Date.ToShortDateString();
- }
- return null;
- }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
Expand|Select|Wrap|Line Numbers
- public class BulkObservableCollection<T> : IDisposable, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<T>
- {
- ConcurrentQueue<NotifyCollectionChangedEventArgs> _losts = new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
- Guid? _cancelToken = null;
- private HashSet<Guid> _cancelTokensDict = null;
- bool _isInAddRange = false;
- bool _isLoading = true;
- public bool IsLoading
- {
- get
- {
- lock (_locker)
- {
- return _isLoading;
- }
- }
- }
- const string COUNT = "Count";
- const string ITEMS = "Item[]";
- public event NotifyCollectionChangedEventHandler CollectionChanged;
- public event PropertyChangedEventHandler PropertyChanged;
- List<T> _items = new List<T>();
- IUpdateTotalItems _updateTotalItems = null;
- public int Count => _items.Count;
- public BulkObservableCollection()
- {
- }
- public BulkObservableCollection(Guid cancelToken, ref HashSet<Guid> cancelTokensDict, IUpdateTotalItems updateTotalItems = null)
- {
- _cancelToken = cancelToken;
- _cancelTokensDict = cancelTokensDict;
- _updateTotalItems = updateTotalItems;
- }
- readonly object _locker = new object();
- public void LoadingFinished()
- {
- lock (_locker)
- {
- _isLoading = false;
- BeginUpdate();
- }
- }
- public void Refresh()
- {
- EndUpdate();
- UpdateTotalItems(_items.Count);
- BeginUpdate();
- }
- #region INotifyPropertyChanged
- private void OnPropertyChanged(string propertyName)
- {
- if (_isDispose)
- return;
- if (!_isInAddRange)
- {
- try
- {
- var ev = this.PropertyChanged;
- if (ev != null)
- {
- ev(this, new PropertyChangedEventArgs(propertyName));
- }
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"OnPropertyChanged:: Error on FirePropertyChanged propertyName = {propertyName}, Error: {ex.Message}");
- }
- }
- }
- #endregion INotifyPropertyChanged
- #region INotifyCollectionChanged
- private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
- {
- if (_isDispose)
- return;
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
- }
- private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
- {
- if (_isDispose)
- return;
- if (!_isInAddRange)
- {
- try
- {
- var ev = this.CollectionChanged;
- if (ev != null)
- {
- ev(this, e);
- }
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"OnPropertyChanged:: Error on CollectionChanged Error: {ex.Message}");
- }
- }
- }
- #endregion INotifyCollectionChanged
- #region Add
- public void Add(T item)
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- // int index = _items.Count;
- InsertItem(0, item);
- }
- public void Insert(int index, T item)
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- InsertItem(index, item);
- }
- private void InsertItem(int index, T item)
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- _items.Insert(index, item);
- OnPropertyChanged(COUNT);
- OnPropertyChanged(ITEMS);
- OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
- }
- public void AddRange(T[] array)
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- if (array == null)
- throw new ArgumentNullException("array");
- BeginUpdate();
- for (int i = 0; i < array.Length; i++)
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- var item = array[i];
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- Add(item);
- }
- EndUpdate();
- }
- public void AddRange(List<T> list)
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- if (list == null)
- throw new ArgumentNullException("list");
- BeginUpdate();
- for (int i = 0; i < list.Count; i++)
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- var item = list[i];
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- Add(item);
- }
- EndUpdate();
- }
- #endregion Add
- #region Remove
- public bool Remove(T item)
- {
- if (_isDispose)
- return false;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return false;
- int index = _items.IndexOf(item);
- if (index < 0) return false;
- RemoveItem(item, index);
- return true;
- }
- private void RemoveItem(T removedItem, int index)
- {
- _items.RemoveAt(index);
- OnPropertyChanged(COUNT);
- OnPropertyChanged(ITEMS);
- OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
- }
- #endregion Remove
- #region Clear
- public void Clear()
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- _items.Clear();
- OnPropertyChanged(COUNT);
- OnPropertyChanged(ITEMS);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
- #endregion Clear
- #region GetEnumerator
- public IEnumerator<T> GetEnumerator()
- {
- return _items.GetEnumerator();
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return ((IEnumerable)_items).GetEnumerator();
- }
- #endregion GetEnumerator
- #region Dispose
- bool _isDispose = false;
- public void Dispose()
- {
- if (!_isDispose)
- {
- _isDispose = true;
- if (_cancelToken != null && _cancelTokensDict != null)
- {
- _cancelTokensDict.Add(_cancelToken.Value);
- }
- if (_items != null)
- {
- _items.Clear();
- }
- while (_losts.TryDequeue(out NotifyCollectionChangedEventArgs item))
- { // do nothing
- }
- }
- }
- #endregion Dispose
- public void BeginUpdate()
- {
- if (_isDispose)
- return;
- _isInAddRange = true;
- }
- public void EndUpdate()
- {
- if (_isDispose)
- return;
- _isInAddRange = false;
- OnPropertyChanged(COUNT);
- OnPropertyChanged(ITEMS);
- OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- }
- public void UpdateTotalItems(int count)
- {
- if (_updateTotalItems != null)
- {
- _updateTotalItems.UpdateTotalItems(count);
- }
- }
- public void Enqueue(NotifyCollectionChangedEventArgs e)
- {
- bool sendToHandle = false;
- lock (_locker)
- {
- if (!_isLoading)
- {
- sendToHandle = true;
- }
- }
- if (sendToHandle)
- {
- Handle(e);
- }
- else
- {
- lock (_losts)
- {
- _losts.Enqueue(e);
- }
- }
- }
- public void PopulateFromLost()
- {
- if (_isDispose)
- return;
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- lock (_losts)
- {
- while (_losts.TryDequeue(out NotifyCollectionChangedEventArgs e))
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- Handle(e);
- }
- }
- }
- private void Handle(NotifyCollectionChangedEventArgs e)
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- return;
- switch (e.Action)
- {
- case NotifyCollectionChangedAction.Add:
- {
- foreach (var item in e.NewItems)
- {
- try
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- _items.Insert(e.NewStartingIndex, (T)item);
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"Handle:Add, e.NewStartingIndex:{e.NewStartingIndex}, Error:{ex.Message}");
- }
- }
- }
- break;
- case NotifyCollectionChangedAction.Remove:
- {
- foreach (var item in e.OldItems)
- {
- try
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- _items.Remove((T)item);
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"Handle:Remove, Error:{ex.Message}");
- }
- }
- }
- break;
- case NotifyCollectionChangedAction.Move:
- {
- try
- {
- if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
- break;
- var itemMoved = (T)e.OldItems[0];
- _items.Remove(itemMoved);
- _items.Insert(e.NewStartingIndex, itemMoved);
- }
- catch (Exception ex)
- {
- Trace.WriteLine($"Handle:Move, e.OldStartingIndex:{e.OldStartingIndex}, e.NewStartingIndex:{e.NewStartingIndex}, Error:{ex.Message}");
- }
- }
- break;
- }
- }
- }