Dhinnesh Jeevan
Dhinnesh Jeevan

Reputation: 549

Showing loading indicator when UI is rendering

Updated entire question to be more specific

I have a WPF application, when being loaded, it displays a circular loading indicator while another thread is getting data from server and manipulating the data.

This WPF application has an ItemsControl with about 500 items bounded to it, with more than 10 different templates. I have a template selector to determine which template to use depending on the item's property.

I have a checkbox, to show and hide certain textblocks in each item in the UI. When I check the checkbox, the UI will freeze for 10 seconds, then starts responding again with the textblocks. If I uncheck the checkbox, it will freeze for about 7 seconds, then the textblocks will disappear.

I know that I should use virtualization, but it does not work for me since each item does not have the same height. I've tried "Standard" VirtualizationMode, it is just slow when scrolling down, not usable for me, and "Recycle" won't work since the items do not have the same height.

Instead of having the UI to freeze for 7 - 10 seconds, I would like to display a loading indicator. I am not asking how to show the loading indicator when loading the app, but how to show the loading indicator when the UI is busy re-drawing.

Please advise, thanks!

Upvotes: 0

Views: 1594

Answers (1)

quetzalcoatl
quetzalcoatl

Reputation: 33506

IIRC there are some, but think about it - when UI is about to render, then the drawing area is dirty and ready to be updated. Update is about to happen. If the update takes a long time (say, 3 seconds), then even if your eventhandler/viewmodel/etc reacts to the "I'm starting rendering" event, what would you like to actually do?

Show busy-indicator? Show? That's a change. It will mark the relevant area dirty, but something already is dirty, redraw is pending, and it will take long time (3s), right? So if you show/hide anything, it will show up after the rendering finishes. That is, your busy-indicator will probably show up after 3s and will immediately hide.

That's not relevant to other things of course.. when rendering starts, you can send a TCP packet, play a sound, display another window/surface with its own independent threds (so they won't wait until first rendering finishes), etc..

I think that what you really need to do is some mixture of:

  • optimize creation and data-bindings
    • do NOT load data in UserControls constructors
    • make all data-bound properties as fast to read as possible, no data-base/network access in getters, etc; return NULL, fire background Task, update property and raise changed even when data is available
    • use OneWay or even OneTime bindings wherever possible
    • don't overuse UI callbacks like events, converters, template selectors; if they are really needed, make them lightning fast
    • (...)
  • working hard on limiting the number of changes to UI elements
    • maybe you dont need all of them?
    • maybe you can merge some of them to get the same effect with less UI objects?
    • maybe you can cache some ready-to-use elements so they won't need to be created?
    • maybe you can cache their visuals? meaning, rendered-bitmap cache, so WPF don't even need to render them, just tell the GPU to use buffered image?
    • if zillion of new UI elements are shown in scrollable area, you can use virtualization on UI or/and DATA elements to only present those things that really are visible right now, and trim off all those that are out of the viewport (WPF has some nice support for that)
    • maybe you can throttle the amount of UI elements to be shown at once? instead of showing a busy-indicator and a zillion of new elements and having them all show up after 3s, maybe show the busy-indicator and first 100 elements, wait some time (or until rendered), and only then start pumping further elements to be shown (again, not all at once, since probably busy-indicator will be animated and it would freeze..)?
    • (...)
  • working hard to simplify UI templates for the zillion of the elements to be show, so they show up quickly
    • limit their depth (like, change a layout of usercontrol>grid>scroll>listbox>panel>[items:usercontrol>grid>listbox>panel>[items:grid>textblock] into usercontrol>grid>listbox>scrollpanel>[items:textblock]]
    • limit the amount of changes to UI elements
    • limit the amount of layout calculations (don't changes sizes if not needed, use constant values for dimensions, margins, etc, instead of calculated wherever possible)
    • use renderTransforms instead of layoutTransforms, if possible at all
    • use complex Brushes (even VisualBrushes) for complex backgrounds, instead of building them as layed-out elements on i.e. Canvas
    • in those zillions of items, limit Bindings and increase of use of resources (meaning, {StaticResource} in XAML etc) as much as possible. If you display 10k elements of different colors, it's sometimes better to define 50 item templates with colors set via StaticResources (so, redItemTemplate, greenItemTemplate, blueItemTemplate, ..) than have one commonItemTemplate that binds all of its colors from itemViewModel
    • (...)

and so on, that's the basics for starters.. Besides some basic points, optimizing WPF may actually be hard. Some further reading below, most of the articles I know from previous experience, helped me to successfully optimize many apps:

And now, to answer your question, though I still think that's not the right way:

  • detect when Rendering starts: just use event CompositionTarget.RenderMSDN
  • detect when Rendering ends: after rendering is scheduled, send a task on Dispatcher with Priority DispatcherPriority.ContextIdleMSDN ; source article

Upvotes: 3

Related Questions