I have a DataGrid which gets populated by instances of class 'Article' and I have DataGridTemplateColumn-s to display data in my custom way. I also have a Loader implemented which is basically an overlay spinner for my long running tasks.
I fetch the list of articles from database in async Task and then I want to render those articles in UI. The problem is rendering the articles on UI takes quite a while (probably because my custom DataTemplates in DataGridTemplateColumns, I have expanders and other stuff there and displaying them takes a while) and it freezes the spinner. Since I fetch the data in Task during that time the spinner works fine, it freezes when it's time to display it on UI. I have tried several different ways, and nothing worked so far.
My initial version:
// Collection bound to my DataGrid's ItemsSource
public ObservableCollection<Article> Articles { get; set; } = new ObservableCollection<Article>();
// Inside the method of fetching arthicles:
public async Task Populate()
{
IsSpinnerVisibile = true;
// The spinner works fine during this time
List<Article> articles = new List<Article>();
await Task.Run(() =>
{
articles = new ArticleRepo().LoadArticles(Users[UserIndex], filter.GetFilterString());
});
// PROBLEM CODE - the spinner freezes for several seconds during this time, when I am adding articles to ObservableCollection
foreach (Article article in articles)
{
this.Articles.Add(article);
}
IsSpinnerVisible = false;
}
I tried switching ObservableCollection to List and just calling property change when list was populated but the result was exactly the same:
// Collection bound to my DataGrid's ItemsSource (changed it to List)
public List<Article> Articles { get; set; } = new List<Article>();
// Inside the method of fetching arthicles:
public async Task Populate()
{
IsSpinnerVisibile = true;
// The spinner works fine during this time
await Task.Run(() =>
{
this.Articles = new ArticleRepo().LoadArticles(Users[UserIndex], filter.GetFilterString());
});
// PROBLEM CODE - the spinner freezes for several seconds during this time
OnPropertyChanged("Articles");
IsSpinnerVisible = false;
}
// OnPropertyChanged() is a method for INotifyPropertyChanged implementation, here is how I do it:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I also tried putting the ObservableCollection filling part in Dispatcher (it was a possible solution I read here):
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate ()
{
foreach (Article article in articles)
{
this.Articles.Add(article);
}
});
The only thing that semi-worked was this solution: C#/WPF Main Window freezes when loading data into datagrid - In the comments it was suggested to put data in collection chunk-by-chunk and calling Task.Delay() in between. Here is how I understood and implemented the solution:
// Collection bound to my DataGrid's ItemsSource
public ObservableCollection<Article> Articles { get; set; } = new ObservableCollection<Article>();
// Inside the method of fetching arthicles:
public async Task Populate()
{
IsSpinnerVisibile = true;
// The spinner works fine during this time
List<Article> articles = new List<Article>();
await Task.Run(() =>
{
articles = new ArticleRepo().LoadArticles(Users[UserIndex], filter.GetFilterString());
});
// PROBLEM CODE - in this case the spinner keeps spinning but its very laggy/jaggedy
int counter = 1;
foreach (Article article in articles)
{
// After 25 item 'chunk' call delay
if (counter == 25)
{
await Task.Delay(100);
counter = 0;
}
this.Articles.Add(article);
counter++;
}
IsSpinnerVisible = false;
}
But in the last solution even though the spinner doesn't completely freeze its spins in very laggy/jaggedy manner, because I assume it only spins during 'delay' time. So it sort of mini-freezes, then keeps going during delay then mini-feezes again and so on. So how should I implement so that the spinner doesn't freeze?