4

We just ported our WinForms application to WPF. However, performance decreased dramatically.

We have a User Interface which consists of about 200 UserControl. Each UserControl is defined by a DataGrid (= 10 columns and 3-15 rows) as well as a Panel which hosts about 10 Buttons.

They are all hosted in a ScrollViewer.

(Please don't recommend to change the UI. I don't have any influence on that. The customer wants to be able to scroll to any of those UserControls.)

Since we ported the whole application to WPF the startup time increased by 100%. Using WinForms we experienced startup times of 15sec whereas now, we are struggeling with 30s.

Do you have any recommandations or ideas how to improve the loading time of a UI which consists of identical UserControl where simply each UserControl is bound to a different ViewModel? (Maybe some fast cloning of the UserControl instances or sth similar?)

I am using static Resources whereever possible. I avoid Grids and Auto Sizing whereever possible.

Hope someone can share some thoughts on that one.

Thanks, TH

TwinHabit
  • 753
  • 8
  • 22
  • 1
    Iam having a hard time to imagine how funny this kind of GUI looks :) I don't believe that all controls have to be visible at once and all the time. – codymanix Oct 19 '10 at 12:44
  • Is the startup time just the form or the entire application? And along the lines of what codymanix said, can you delay population of some of the controls until after the window is up (perhaps async in the background)? – RQDQ Oct 19 '10 at 12:56
  • It's the startup time of a single form. Some wait animation already shows up to explain that some loading process is going on. All UserControls are hosted by a ScrollViewer. Maybe I could display a first bunch, display the View and then add the missing UserControls one after another... but the ScrollViewer would still be not really usable until all Items are added... – TwinHabit Oct 19 '10 at 13:04
  • Sure you can use the scrollviewer, even if not all controls are inside. You simply set the size of the content panel the size that it will have as if all controls would be already added. The in the scroll event check if you scrolled to a position where controls are still missing and then display them. – codymanix Oct 19 '10 at 13:09
  • @codymanix: The only problem I see about that approach is that I don't really want to calculate the height of the complete Panel. As every grid in every UserControl can have a different amount of rows, the heights are different. Of course, I can make an estimated calculation but that doesn't sound too beautiful to me? – TwinHabit Oct 19 '10 at 14:02
  • It's quite possible WPF and the controls are not the problem. Before you get involved in fixing them, you should find out for certain if they are the problem. It's easy enough to find out. – Mike Dunlavey Oct 19 '10 at 14:20
  • I changed the DataTemplate of the Collection to use a simple Button instead of my expensive UserControl. => Loading time got reduced from 30sec to 8sec. So I am pretty sure that it's the controls? – TwinHabit Oct 19 '10 at 14:40
  • @TwinHabit: Yeah I know, that's an enticing data point. It seems to narrow things down, but maybe you really wanted that "expensive" UserControl, no? Pausing will pinpoint the issue, not just suggest where it might be. It could be something as simple as fetching strings from resources. – Mike Dunlavey Oct 19 '10 at 15:41

5 Answers5

2

First find out what is responsible for the time.

Maybe it's the controls, and maybe not. Often it's data structure.

I use the random-pause method.

Community
  • 1
  • 1
Mike Dunlavey
  • 40,059
  • 14
  • 91
  • 135
  • I profiled the application already. 40% of the time is lost anywhere in the WPF. Do you know any non-sampling profiler that can tell me what methods of the .NET Framework consume the time? – TwinHabit Oct 19 '10 at 14:14
  • @TwinHabit: The thing to do is run it under the debugger, and while it's being slow, hit the pause button, and study the call stack. Take the time to understand why it's doing whatever it's doing. Do this 5-10 times. It will show you what the problem is, guaranteed. – Mike Dunlavey Oct 19 '10 at 14:32
  • @TwinHabit: What you need to know is which lines in your code are on the stack most of the time and why. Examples: If it's painting, maybe you only need to suspend painting until all controls are up. If it's building big data structure and getting it all cross-linked, that's a different problem. Maybe both are happening. Maybe something else entirely. – Mike Dunlavey Oct 19 '10 at 14:56
  • I followed your advice. Unfortunately the problem really is in WPFs layouting / rendering of my UserControls. I loaded all ViewModels / Resources in advance and later added the whole picture to the View. My performance profiler is telling me the same: None of my line of codes are executed during this slow phase. So I have to find a way to speed WPFs layouting / rendering etc. up. Does anyone know any common pit falls?For example:Is it possible to tell the ListBox that it should first load every UC into video memory before it is displayed?Or is it possible to deep clone UCs or anything similar? – TwinHabit Oct 21 '10 at 13:51
  • @TwinHabit: Hmmm... Ok, I'm getting out of my depth here, but in plain old Win32, there's a way to suspend painting until all controls are in place, for example by hiding the top window. For layout, I can easily see that getting out of hand. I really hate to guess, but maybe turning off layout would make a difference. – Mike Dunlavey Oct 21 '10 at 14:05
  • I just figured that the most expensive work that is being done is the layouting. MeasureOverride and ArrangeOverride of each of my UserControls is called 5 times! Usually only one call to each method should be enough, as soon as every child has loaded its ViewModel and therefore knows how much space they require... Damn... – TwinHabit Oct 22 '10 at 06:39
  • I just tried to replace the ListBox which hosts all my UserControl with a Canvas. I thought, as I can calculate the absolute width and height of my UserControls I can speed the layouting up by simply setting each UserControls width/height manually and add the Control to the Canvas settings its Canvas.Top property. I got quite shocked that this _increased_ performance by another 50%!! How can that possibly be any slower than what the non_virtual ListBox is doing? I think I have to go with the Canvas solution anyways because the ListBox recreates each control when the ItemsCollection is resorted – TwinHabit Oct 22 '10 at 08:30
2

My final solution is a custom Virtual Panel which supports items of dynamic height.

See http://rhnatiuk.wordpress.com/2006/12/13/implementing-a-virtualized-panel-in-wpf/ on how to create virtual panels.

My UserControls support two states: - Initial - Loaded

When the application is in idle the virtual Panel asks the Controls to change to the "Loaded" state. This loads the expensive UserControl.

Like that everything is lazy loaded and as soon as the user stops scrolling the visible items are loaded.

Maybe that helps others that are in the same sitaution. TH

TwinHabit
  • 753
  • 8
  • 22
0

Try only to create the controls which are visible at the time, use lazy loading.

Maybe SnapsToDevicePixels=true can also help a little bit.

codymanix
  • 28,510
  • 21
  • 92
  • 151
0

Guys, I thought about the following implementation. If anyone has concerns please let me know:

I will implement my own virtualizing "StackPanel" which supports smooth scrolling.

For the moment we assume that the height of my UserControls is fixed. One page could possibly hold 5 UserControls.

I will then go ahead and cache the preceding as well as the proceeding page:

In memory I will always hold 15 UserControls. The content of the ScrollViewer is a Canvas. Locations of my UserControls are adjusted by setting Canvas.Top. Let's say the current situation is the following:

User has scrolled to page 2. That means UserControl 5-9 is visible. Now the user scrolls down. As soon as UserControl 5 becomes invisible I take the UC of the top (in this case UserControl 0), change its ViewModel and adjust its Canvas.Top so that it now is the Control which is at the End of the ControlCollection. If the user scrolls any further I take UC 1, change its ViewModel and adjust its Canvas.Top. And so on.

Furthermore I will set Canvas.Height manually so that the ScrollViewer represents the ScrollBars in a correct way.

I hope my explanation is understandable :)

What do you think?

BR, TH

TwinHabit
  • 753
  • 8
  • 22
0

I remember reading something about how each instance of a UserControl loads the resource dictionary. So if you have as many of these as you describe it can take a while to load those.

Unfortunately I can't find the link I remember, but here is one that might help: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/c9b6aa9f-5a97-428c-8009-5cda432b820c

Another thing to try is to not use UserControls and instead use a DataTemplate to build your datagrids and buttons. Or make a custom control template for the datagrid that includes the buttons. Either one might be faster.

Hope this helps.

dex3703
  • 2,077
  • 4
  • 28
  • 45