How not to block the UI while instantiating a lot of controls - c#

There are lots of articles about how to keep the UI thread free in WPF. Mostly, using async-await to perform long operations in the background. But what can I do when the UI generation itself is heavy?
Say I have a view model that holds a list of items in a property called "Items" of type List, and a view that presents them using an ItemsControl that is bound to that list and generates ui for each item using a data template. I am reading information from the server and while I am getting the data, there is some sort of animated "Busy" indicator that is bound to a IsBusy property in the view model. All is well with that approach and it keeps the UI responsive.
Once the data had returned and the task is done, I am also generating the observable objects (ItemType) for each item in a background task (since no UI is bound to them yet, this is not a problem), and copy the information from the result that came from the server. Still - all nice and responsive.
The last step must happen in the UI thread. Once the List is ready, placing it inside the Items property yields a notification that causes the ItemsControl to start generating UI for each item, and this happens (and must happen) on the UI thread. Since this is a heavy task (there are not a LOT of items but each items has a pretty heavy UI template), the UI hangs and the busy indicator no longer animates.
My question is this: is there a way to keep the UI responsive while the items control is populating? This currently takes between 0.5 - 3 seconds and its too long a task for the UI to remain stuck.
EDIT:
Just to make it clear - this is not a question about Virtualization. I have no problem with the view taking long to create, I just want to find a way to keep to UI responsive while it is being created. I thought about perhaps drawing it while its hidden, or even collapses, but it does no change anything since the UI thread is still busy while doing that, and the busy animation still gets stuck, and the entire UI along with it.
Thanks

Yes. Don't draw all the items at once - only draw those you need to draw immediately. This is otherwise known as UI virtualisation.
Microsoft give a good example of how to implement virtualisation using a canvas at the following link: Virtualized WPF Canvas
Update after comment:
If you hide the panel, the items are actually drawn on to the panel but are invisible. To stop the items appearing one by one, set the panel to Visibility.Hidden and then to Visibility.Visible once you've drawn the items. The panel appears blank and then is suddenly populated.

Related

Why would one control get redrawn more frequently than another when both are being invalidated?

I have an winforms application with two controls in which I do custom drawing. The main control shows a small section of a long continuous image of a road, while the other control shows a horizontally-squashed representation of the entire road image. The second control is used to navigate the first: you can click anywhere in the overview to scroll to that location in the main view. Here's a screenshot, where the main view is on top and the overview is on bottom:
There's a cyan line in the overview control that indicates the position of the imagery being shown in the main view. (In this screenshot, it's just over 1/3 the width from the left.) The user can click and drag that line in the overview, which will cause the main view to scroll.
I've noticed something odd about the redrawing: When I'm dragging the cyan line in the overview control, the main view gets redrawn much more often than the overview, even though both are invalidated in response to the mouse move. I know that WM_PAINT messages only get sent when there are no other messages in the queue, but I don't understand why one control would get redrawn more often than the other if they're both being invalidated at the same time.
Actually, as I was typing that last sentence I think I stumbled on the reason. Tell me if this sounds right:
Each control has its own message queue, and the main control isn't receiving any input-related messages, so its message queue is empty more often than that of the overview, which has to process all the mouse events. So it receives the WM_PAINT message more often than the overview.
So at this point, I guess the question is, "Does that make sense? Is that what's happening?"
The theory is not sound, there is only one message queue per thread. What you see is almost certainly caused by the way WM_PAINT is generated. It is only delivered when the queue is empty. That makes it a "low priority" message, user input always goes first. Important that it works that way, you would not want user input to get lost, or the message queue to explode, because painting code is slow.
So, roughly, you called Invalidate() twice. The bottom-most window gets the Paint event but by the time it is finished there is yet another mouse event waiting to be processed. So the second window does not get its paint event and you'll invalidate a window that is already invalidated. Only when you slow down the mouse, or stop moving it, can it catch up.
Easy to see from Task Manager, you'll see the UI thread of your program burning 100% core.
Forcing a paint anyway is possible, but you have to call Update() instead of Invalidate(). Your program still burns 100% core but now instead of skipping paints it will be less responsive to the mouse. That sounds dangerous, with the potential to flood the message queue, but it is not. WM_MOUSEMOVE does not get added to the message queue either. Like WM_PAINT, it only gets generated when the queue is empty. You'll get it first.

Using CacheMode From Worker Thread

I'm working with a WPF Canvas that contains a lot of elements. It needs to be able to pan and zoom. WPF cannot handle the layout of the canvas since it has too many elements (I don't know how many, but the XAML file is above 20mb).
The canvas is zoomed and panned using scale and translate transforms on the canvas' rendertransform.
A good trick I found is to use CacheMode. This fixes all panning problems as I generate a cache for the zoom level and set that. Easy, like this:
canvas.CacheMode = new BitmapCache(scale);
The problem is that I need to do it every time the scale changes (which is when the user zooms).
I understand that it will take some time to render the canvas and that is probably unavoidable. But how do I move that rendering to another thread so that it doesn't block the whole UI?
I understand the concept of Background Workers and Dispatcher and such, but I'm not sure how to handle this when the task that actually takes up all the time is the setting of a property that I can only access in the UI thread.
Is it possible to somehow generate the cache in another thread and then transfer it to the UI somehow?
Any other good ideas on how to lessen impact on the UI?

WPF Datagrid Multiple Synchronised Frozen, Collapsable Panes separated by GridSplitter

I have a requirement to create a "super grid" as part of an application, which will consist of multiple frozen and scrolling panes. The grid also has the requirements that it must support the following features:
Display a datagrid with 100s of rows with “reasonable” scrolling performance
Group by a certain field, collapse/expand grouped
Display any control inside a field, e.g. images, text, combobox, button
Allow buttons and editing of fields as well as read-only mode
Allow multiple vertical split panes (or workaround, multiple synchronised grids)
User column re-ordering with persistence
User column sorting ascending/descending
Dynamic insertion/deletion of columns (column choosing)
4th dimension below grouping – RowDetails template on selected row
Allow Flashing / animated cell/row templates on value update
My initial feasibility suggests that the WPF Datagrid is capable of almost all the requirements above (with heavy templating), however the multiple vertical split panes may not be covered. Below is a diagram of what I'm trying to achieve:
I would like to enquire if anyone has done this before and if so, what grid technology you have used.
Can the above be done in WPF Datagrid? I am aware of Frozen columns however this appears to apply to freezing the first N columns only
Can the above be achieved with multiple grids and synchronizing the Vertical scroll positions?
If so, will this solution still be virtualized?
Are there any third party grids you can recommend to achieve this instead? Please note we will be heavily styling the grid according to in-house branding so it must be flexible and Blendable
Any comments/suggestions appreciated.
Update - Feasibility Investigation Results
I have a working prototype for this using 3x grids and collapsable grid splitters between them.
The scrollviewers are synchronized using the answer from this question.
Selected row synchronized across three grids by binding to a common SelectedIndex property with TwoWay binding.
I will be working on grouping and rowdetails templates next... Thanks to answerers for your points! :)
I've implemented most of your requirements and more with the devexpress wpf grid control. It's not free and there's a bit of a learning curve (their documentation is essential), but it'll save you so much time in the long run...
I've done this with a standard DataGrid with perfect performance and ultimate flexibility, but it requires a sound architecture. Both data and UI need to be virtualized - no control can annul the cost of sorting a large set of data (for example), so if that sorting is happening on the UI thread, the user will notice. If your back-end supports paging and filters, your front-end can virtualize and the user will never know that only 100 rows have been loaded out of their 10000. As they scroll, pages of data are loaded & unloaded in the background and the UI never ceases to be responsive.
However, knowing where you work leads me to suspect that your front-end is the real workhorse...

What are the side effects of Control.Controls.Add in Winforms?

I am trying to dynamically create a rather large TableLayoutPanel for an application I'm working on. As I've read from other questions, the TableLayoutPanel class suffers from significant performance problems when handling large and changing contents; I have, however, come too far to switch to something else just now.
I have been able to get around most of the issues by suspending layout before adding controls to the individual cells. One problem remains - when the table is first shown, it appears to take a little time before the background colors (just white) and everything else ready to be shown - like just a little less than a second of ugliness. I have tried doing all layout on startup, so that no calculations remain when the control is finally added, but the first drawing of it remains ugly. The weird thing is that when the control is shown again later (I keep it in memory instead of recreating it all the time) after having been removed from the main form for a while, it appears right away without any layouting. This leads me to believe that something happens when I first add the control to a child control of the main form, something I haven't reproduced in my own layouting - the question is, what? In order to force layout of the control right after its initialization, but before it is first shown, I do the following:
table.SuspendLayout();
// Create a whole lot of controls here and add them to the TableLayoutPanel
table.ResumeLayout();
table.Size = reportControl.Panel2.ClientSize;
table.PerformLayout();
table.SuspendLayout();
// Add the TableLayoutPanel to the main form here
Shouldn't that be enough? Unless I am very much mistaken, I do not touch the TableLayoutPanel between this code block and when it is added to the main form. Still, when it is first added, it looks way different than the second time. Oh, and the TableLayoutPanel is double buffered, so that's not it I think.
The extra thing that happens when you call Controls.Add() is that the native Windows window gets created. Plus the windows for all the controls you put in the panel. Which tends to be slow because TLP is often stuffed to the gills with controls. This doesn't happen when you Remove() the control and don't Dispose() it.
You can create the window early by calling CreateControl(). It will take just as long, but maybe isn't as noticeable.

Prevent a WinForms PictureBox animated GIF from pausing during processing?

I have a form with 2 controls on it. Control 1 has several search criteria and the user can click a button to start the search. Control 2 has a GridView that will display the search results and also has a PictureBox with an animated Gif in it.
Control 1 has a method in it that has a nested foreach loop that takes upwards of 5 minutes to complete (depends upon the search criteria, sometimes it finishes almost instantly).
What happens is that I call a method on Control 2 that makes the PictureBox visible and the animated Gif starts running. As soon as the foreach loop is hit that takes a while, the image completely stops. Whenever I show any MessageBoxes the image starts running again (I presume it is because the MessageBox is blocking). As soon as the MessageBox is closed, the image stops animating.
I presume I will need to utilize something with threading, but everything I am trying (ImageAnimator, Thread calling a method that calls Invoke, etc.) all seem to not work. Any hints what events/libraries I need to work with in order to make this work? Any good examples to follow?
You long running code runs in the GUI-Thread and blocks the internal message loop, so any GUI is stopping. Try to run your long running code in a separate worker thread.
2 words: BackgroundWorker class.
http://msdn.microsoft.com/en-us/library/waw3xexc.aspx

Categories