Datagrid binding (WPF) causing UI delay - c#

I want to be able to add items to a Data grid at a fast rate without causing UI delay.
Here is what I am doing now:
I am using an ObservableCollection that is bound to the data grid.
I use a background thread that loops and calls Invoke on the current dispatcher only when inserting/removing from the observable collection. Calling BeginInvoke instead has undesirable results.
I know that invoking that much on the dispatcher is causing the delay but I don't know what else to do. I have used background workers before but I don't think that applies to my scenario.
What can I do to keep the UI responsive?

Batch the updates -- the background thread could add items to a queue and you can periodically refresh your bound observable collection by invocation. Take a look at the System.Collections.Concurrent namespace if you need to handle multi-threaded producers

A major weakness in your design is that by binding to an ObservableCollection, you are causing the UI to render every item that get's added to the list (possibly thousands) - even if at the end of processing there are only 10 items which need to be rendered.
I saw huge improvements by changing the ObservableCollection to a List, and refreshing the DataGrid manually at the end of processing - this way the UI only ever needs to process 10 items. I found that this change caused a 50% performance gain, as well as allowing the UI to be 100% responsive while the List is being processed.
If you are processing the list for a long time, and need to show live changes, you could refresh the DataGrid every 100 items. This would show results with about 0.5 second accuracy which should be close enough.

Related

How to Change DataGrid ItemsSource Without Freezing the UI Thread?

I have a list of Customers and I update my DataGrid's ItemsSource to contain the customers list.
The problem is that after changing the ItemsSource, the thread update all binding targets to the values in each customer object, and this action freezes the UI for more than 30 seconds.
To solve this I tried to change the ItemsSource property in another Thread but the new thread can not access the datagrid control which owned by the UI thread.
If I put the ItemsSource changing code in Invoke methode the UI will freeze again.
//The following code is handled in another thread
// Geting a list of Customers
List<Customer> customers = manager.AllCustomers.SearchCustomers(fName, lName, address, city, tz, phone);
//Changing the DataGrid ItemsSource
Dispatcher.Invoke(() => customersSearchResultsDG.ItemsSource = customers);
//UI Thread is freezing until ItemsSource Changing complete
p.s
The amount of time it thinks not really bother me because I created a loading animation control.. but the freezing makes the animation stuck and that is what i'm trying to fix.
When the UI thread in a WPF Application is busy, the UI will appear to freeze. There is nothing that can be done about that, but to avoid this situation, we typically do as much long running work as possible in background threads. However, as you have discovered, you cannot manipulate UI elements from a background thread, so there is only so much that we can do.
Other things that we can do to minimise the amount of time that the UI thread is busy is to use Virtualization, container recycling, freezing resources and/or optimise used Templates. You can find out more about this from the Optimizing Performance: Controls page on MSDN.
However, with the small number of items in your collection, it would seem to me that either you are loading and/or preparing your data extremely inefficiently, or you have incredibly large and complex DataTemplates to define your items. The links provided above should help you if your problem is the latter.

How to reduce TreeView population time on UI thread?

I'm making a media stats application and it has a TreeView control with all media files as items. It takes several seconds to populate it and I'm thinking there has to be another way to show items at any scrollbar position without taking that much time.
Accessing controls from threads other than those they were created on (UI thread) is prohibited, so it's either the usual approach with a noticeable freeze, or some other way I can't come up with.
How do I populate a TreeView control with many items without stalling the UI thread for a long time?
Don't load all of the items at once. Load only what they need to see at any given point in time (so at first, only the top level), and then when any given item is expanded (there is a relevant event for you to add a handler for), dynamically populate the next level. When an item on that next level is expanded, expand one level down, etc.
Use your UI thread's Dispatcher from another thread with a lower priority.
Or you could implement it differently. Show VMs linked to the Items, which are still loading in the background, and update the VM when the item is finished loading.
Or only load X amount at a time, pages of items.

Multithreaded, Multi-Dispatcher WPF app still draws in a single thread?

I have a WPF & C# app that contains a large DataGrid, about 35 cols x 50 rows, to monitor a bunch of frequently changing values. The trouble is, when the whole grid is visible, refreshing it hangs up the user interface for almost a second. I refresh it explicitly every two seconds, which is fine for what I'm doing, but having the rest of the UI hang up is a real pain.
OK, so I decide I'll run the rest of the UI in a separate window in a separate thread with a separate Dispatcher. I wrote a toy window containing just a textBlock with an incrementing count that updates 10 times a second using a DispatcherTimer. However, rather than the count incrementing smoothly, it pauses while the grid refreshes, then resumes displaying with the count about 10 higher than it was when it paused, so timer events are getting handled. I'm just not seeing a refresh.
Does WPF only draw all its elements in a single thread? Any way around it?
Here's my 2nd window creation code:
private void Window_Loaded( object sender, RoutedEventArgs e )
{
ThreadStart ts = new ThreadStart( RunSpareThread );
m_spare_thread = new Thread( ts );
m_spare_thread.IsBackground = true;
m_spare_thread.Priority = ThreadPriority.Highest;
m_spare_thread.SetApartmentState( ApartmentState.STA );
m_spare_thread.Start();
Dispatcher.Thread.Priority = ThreadPriority.Lowest;
}
void RunSpareThread()
{
m_spare_window = new SpareWindow();
m_spare_window.Show();
Dispatcher.Run();
}
FYI I've tried implementing the grid a few different ways - as a ListView, as a Canvas that overrides OnRender and draws a whole bunch of GlyphRunDrawings - WPF is just incredibly slow at drawing this stuff.
Unfortunately yes. That said there are a number of things you can do to increase the responsiveness of your ui. One of the main things is to make sure that the least amount of work is being done in the UI thread. This means doing all of the DB reading etc. within a separate context. You should also look into how your grid is displaying your values - is it virtualizing? Then there is also how you are databinding, a bindingsource should allow you to only update the binding after all of the changes are finished.
WPF is very fast if you use it properly.
Some tips:
Implement model (or ViewModel in terms of MVVM) of the data you want to display. Be sure that it implements INotifyPropertyChanged interface. Bind collection of such models to your DataGrid;
Do not refresh all data each N seconds. You should somehow detect data changes (using working thread) and update appropriate properties on appropriate models. Because of data binding, all changes will be automatically reflected on the DataGrid. So, you don't even need to use Dispatcher (at least explicitly);
Use ListView instead of DataGrid if you don't need to do special actions with data (like editing, sorting, filtering, etc.);
Consider implementation of a virtualization if you need to display a huge amount of rows.

Possible to pre-empt the Windows message loop?

I have a wpf application that populates an Infragistics XamDataGrid by the usual method of binding an observable collection to the grid. As each data item is populated into the collection, one at a time, the grid updates. I also have a cancel button that I would like to immediately stop the population if the user clicks it. However, it takes a few seconds or more to respond to the cancel.
The problem (I think) is that the message loop is full of the grid population events and my cancel is way in the back and must wait its turn. I was wondering if there is a way to insert a message in the front of the queue and thus make the cancel more responsive (hacky or not - if hacky, please explain what ill effects I can expect).
I am not experiencing bad performance; in fact the UI is quite responsive. The problem is purely that the cancel event has to wait its turn in the message queue and I would rather it have priority over the population messages.
edit: clarifications
When you say Grid, what kind of Grid do you mean? I think your issue might be that for a large collection, this setup might not use any type of item virtualization. You might be better off using a ListBox or ListView in this case, which can use a VirtualizingStackPanel to generate UIElements for only the on-screen items.
If this is the case, your ui thread is getting bogged down because its generating elements for every new item, whether or not it's being displayed on screen. If you're using a 3rd party Grid, you might check to see if it has any virtualization functionality built in that you can turn on.
The answer ended up being the DispatcherPriority:
private void btnCancel_Click(object sender, System.Windows.RoutedEventArgs e)
{
Dispatcher.Invoke(new Action(() => btnCancel.Command.Execute(null)), DispatcherPriority.Send);
}
"Send" priority is the highest of an enumerated type, with "SystemIdle" being the lowest. When I invoke the button's command with that priority, the Cancel goes through immediately.

Keep UI Thread free

I have a form with two panels(top, bottom), each panel contains grids. Basically, it's a Master-Detail form where selecting a row from the top grid would show details in the bottom grid.
Binding the data to the detail grid is taking some time. Since binding is done on UI thread, it blocks the thread and therefore the user cannot select another row from the master grid until the binding is done.
Please note that by binding I don't mean getting data from data source. It's the actual binding that's taking longer as it does a lot of data massaging. How can I keep the UI thread free while the detail grid is doing it's binding?
Thanks a million.
You can't. The update of the UI has to be performed on the UI thread.
You may be able to speed up the binding by using things such as BeginUpdate/EndUpdate which is available on some controls but as you don't specify what you are using I can't say if that's available.
You can use a background thread to perform your data retrieval, then use the UI thread to display it.
If I were you, it sounds like you are dealing with a LOT of data, and I would separate all the "massaging" you can into a separate thread process.
So maybe for example when a master record is created, you "manually" spin off the detail data in a background thread to another dataset and do the massaging, then just bind the resulting dataset to the grid. That way the only thing taking place on the UI thread is just the UI binding.
Ultimately if it's taking that long, you may be approaching a critical point in your application where you need to manually do what you need to do in code rather than using the out of the box data binding features in .NET.
Finally I found the solution. The solution doesn't include multithreading to start with. As I said that the delay was in binding the grid meaning the main thread was held, we couldn't do much. So the solution is to bring delays. When user selects the master row, a timer ticks off for a certain time. If another request is made before the time is expired, timer gets restarted. This is we ignore all calls that are made because user was clicking or selecting rows too fast. Once timer is expired, I take the selected row and display data. Simple and elegant solution.

Categories