How to reduce TreeView population time on UI thread? - c#

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.

Related

WPF "Expand All" Control Load Time

I have integrated a feature in my tool that allows me to expand all recursively. I realise this would generally be a slow process anyway but currently. I have maybe 100 nested controls which are all expanded at once, so calling Expand All means that each control/datatemplate etc is processed at once.
This causes the tool to lock up for a good 10 seconds before responding. Of course, once the view hierarchy has been constructed, its then fast (I can collapse and expand instantly from then on). But it seems a little odd that there isn't a faster way to generate large forms, I wouldn't consider 100-500~ controls very many.
I have looked into virtualisation, but it doesn't appear to be useful to me in this case because all controls are expanded at once. Its fine if I expand them on a singular basis.
Edit:
Updates we're needed to the question to describe what the layout of my window is:
I have a number of TextBoxes, ComboBoxes, Sliders which are all nested inside of a number of Expanders. Each expander can contain N number of expanders, this is recursive to an extent. Depending on how the data is laid out. Some of these data types (which are represented by datatemplates) may contain StackPanels if needed and numerous grids for layouts.
To expand all it simply means to iterate each expander (which IsExpanded is bound to an IsExpanded property). I set this property on each data type (and its children) to true. And let the binding do the work of expanding everything. This causes every single control to be added at once.
Thanks
So after reading the edited question, I have a guess what's going on:
The UI is busy on rendering controls. And I assume there's not much data kept behind in viewmodels bound, so the data is not the point in your issue.
When the Expander control gets IsExpanded = true the first time, Xaml is parsed (object / resource parsing, not compiling) and visual tree children are created.
100-500 visual tree elements is a high number for rendering, because each of them (if it's belonging to the System.Windows.Controls namespace) consists of many sub-controls (Border, Grids, TextBox,..).
What you can achieve is NOT shortening the expanding time, but to unblock the UI:
advise the dispatcher to handle IsExpanded asynchronously and see how it behaves. This requires that you don't use the expanders default action:
In the arrow button click handler, skip default handling and insert this (IsExpanded is your bound viewmodel property here):
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(
() => { this.IsExpanded = true; } ));

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.

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.

Datagrid binding (WPF) causing UI delay

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.

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