Virtualization Performance Issue with Large Scrollable Data SL4
Asked Answered
Q

5

12

Problem: Displaying large amounts of data in a scrollable area has horrible performance and/or User eXperience.

Tried: Basically set a DataTemplate in a ListBox to show a grid of populated data with the VirtualizationMode set to Recycle and a fixed height set on the ListBox iteself. Something like the example below.

 <ListBox x:Name="Items"
      TabNavigation="Once"
      VirtualizingStackPanel.VirtualizationMode="Recycling"     
      Height="500">         
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="0,5">
                        <HyperlinkButton Content="Action" Margin="5"/>
                        <ContentControl  
                                cal:View.Model="{Binding}"  
                                VerticalContentAlignment="Stretch" 
                                HorizontalContentAlignment="Stretch"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

The ContentControl would be bringing in a standard <Grid> from another view that formats the overall layout of the populated items consisting of around 20 static, and 20 data bound TextBlocks.

This works alright, and cut the initial loads in half. HOWEVER, now the problem is I need the ability for the height to NOT be a fixed size so it takes up the space available in its parent and can even be resized. Thanks to @DanFox I found out you have to fix the height in one form or another to invoke the virtualizing or the RenderEngine just thinks it has infinite room anyway.

The Question is: Is there a better way to do this, or how can I at least fix the current technique to allow for better UX? I'm generating potentially hundreds of these items so I need the performance enhancement of virtualization. However I also need to allow the user to resize the window and retain the ability to scroll effectively.

Any insight is greatly appreciated, thanks and Happy Holidays!

Quantifier answered 14/12, 2012 at 4:27 Comment(8)
Have you tried temporarily fixing the height of the ScrollViewer and other components? Sometimes the dropoff in performance can be because the layout engine is giving an infinite height to the ScrollViewer. Sorry I can't be more explicit, I'm a little rusty in this area, haven't done any SL for a while now...Ennis
Is your data loading quickly? What collection type are you bind to?Immoderacy
@Dan Fox So just hard fixing the height of the scrollviewer could effect the rendering speed?Quantifier
@Big Daddy the data retrieval is less of the concern as it appears the display is what's slowing things down.Quantifier
I've definitely seen it before - basically if the layout engine is determining the height of the control is "infinite", it doesn't virtualize properly. Also, reading around (like [link]dzone.com/articles/virtualization-wpf) it looks like the CanContentScroll might need setting - this might not apply to SL thoughEnnis
@DanFox Ah ya I see what you mean. Is that literally accomplished as easily as setting a fixed height to the parent control though? Was also thinking I would exchange the ItemsControl for a ListBox and use the Virtualizingstackpanel as the item template.Quantifier
Yep - it's as easy as that. Messy but proves the point. Also, it's well worth putting a profiler on the software to see where the time is being taken up - if it's in the constructors of the templates, clearly the virtualization isn't behavingEnnis
@DanFox How about if you want the height to be dynamic? Are you just SOL? The ideas is I'd like to be able to allow resizing of the window to an extent, but can't accomplish that with fixed sizes. I tried to at least bind the height to it's own actualheight, and to a container grid but it doesnt behave as expected either. On that note, at some point you'll have to post this stuff as an answer so I can at least award some points for the troubles :)Quantifier
Q
1

Ok so here's what I ended up doing and have to say it's a huge improvement! We started with a load time of 77seconds on the dot for this particular piece. With some refactoring on how we were binding on the back we shaved about 20 seconds off, so the problem was still in the UI rendering. The answer below is obviously more simple than I originally thought it would be and now we're loading gigantic amounts of data in 15-20 seconds (with virtualization of course) and smaller loads are basically instantaneous putting us back on track.

Here's what I did;

 <!-- This needs to be contained in a parent panel like a grid -->
 <ListBox x:Name="Items" >
                <ListBox.Template>
                    <ControlTemplate> <!-- Broke it out to allow resizing -->
                        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                            <ItemsPresenter/> <!-- This little fella does magical things -->            
                        </ScrollViewer>         
                    </ControlTemplate>      
                </ListBox.Template>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel VirtualizingStackPanel.VirtualizationMode="Recycling"/>  <!-- Recycle was a must -->        
                    </ItemsPanelTemplate>       
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <HyperlinkButton  VerticalAlignment="Top" Margin="5" />
                                <!-- This guy I did need to set a minwidth on to retain nice and predictable scrolling 
 when datacontext was potentially changing -->
                            <ContentControl cal:View.Model="{Binding}" MinWidth="1160"
                                            VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
                        </StackPanel>
                    </DataTemplate>         
                </ListBox.ItemTemplate>           
            </ListBox>

And that's it! Voila! All it took was to break out the ControlTemplate and apply a direct ItemsPresenter! It now virtualizes great, it resizes, balance has been restored to the universe and I no longer want to punch a unicorn. After that I just stripped the visual states because we didn't need any kind of item selection and that's about it, thanks for everyones insight! Sorry I couldn't award a bounty this time.

Quantifier answered 26/12, 2012 at 17:11 Comment(0)
E
1

as requested :-) I didn't feel like I'd "answered" anything so far...

You're absolutely right, there should be a way of getting the height dynamic. Setting it to be fixed height is a quick check just to make sure the virtualization is working. Did you get anywhere with the performance profiler, or maybe using SilverlightSpy? I have made one of these issues go away by refactoring the binding on the UI/VM to make it more efficient. There was a good resource for WinPhone7 SL on this with a potentially good solution, I'll see if I can dig it out (the theory should transfer to full-fat SL)

This has a good point on caching depending on your VM architecture

This might be useful as it explains laxy loading a little more

And a couple of hints from the WinPho 7 dev team.

Hope that helps

Ennis answered 21/12, 2012 at 9:57 Comment(0)
Q
1

Ok so here's what I ended up doing and have to say it's a huge improvement! We started with a load time of 77seconds on the dot for this particular piece. With some refactoring on how we were binding on the back we shaved about 20 seconds off, so the problem was still in the UI rendering. The answer below is obviously more simple than I originally thought it would be and now we're loading gigantic amounts of data in 15-20 seconds (with virtualization of course) and smaller loads are basically instantaneous putting us back on track.

Here's what I did;

 <!-- This needs to be contained in a parent panel like a grid -->
 <ListBox x:Name="Items" >
                <ListBox.Template>
                    <ControlTemplate> <!-- Broke it out to allow resizing -->
                        <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
                            <ItemsPresenter/> <!-- This little fella does magical things -->            
                        </ScrollViewer>         
                    </ControlTemplate>      
                </ListBox.Template>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel VirtualizingStackPanel.VirtualizationMode="Recycling"/>  <!-- Recycle was a must -->        
                    </ItemsPanelTemplate>       
                </ListBox.ItemsPanel>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <HyperlinkButton  VerticalAlignment="Top" Margin="5" />
                                <!-- This guy I did need to set a minwidth on to retain nice and predictable scrolling 
 when datacontext was potentially changing -->
                            <ContentControl cal:View.Model="{Binding}" MinWidth="1160"
                                            VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" />
                        </StackPanel>
                    </DataTemplate>         
                </ListBox.ItemTemplate>           
            </ListBox>

And that's it! Voila! All it took was to break out the ControlTemplate and apply a direct ItemsPresenter! It now virtualizes great, it resizes, balance has been restored to the universe and I no longer want to punch a unicorn. After that I just stripped the visual states because we didn't need any kind of item selection and that's about it, thanks for everyones insight! Sorry I couldn't award a bounty this time.

Quantifier answered 26/12, 2012 at 17:11 Comment(0)
M
0

If its only due to the fixed height, why dont you just try this:

<Grid>    
<ListBox x:Name="Items"
      TabNavigation="Once"
      VirtualizingStackPanel.VirtualizationMode="Recycling"     
      Height="{Binding Path=Height,RelativeSource={RelativeSource AncestorType=Grid}}">         
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" Margin="0,5">
                        <HyperlinkButton Content="Action" Margin="5"/>
                        <ContentControl  
                                cal:View.Model="{Binding}"  
                                VerticalContentAlignment="Stretch" 
                                HorizontalContentAlignment="Stretch"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
</Grid>

Important that you the ListBox into a container that fits to outer space and not to inner space like a stackpanel.

Try this and tell me if it helps. :)

Mondragon answered 21/12, 2012 at 10:40 Comment(6)
I might be wrong, but I don't think you can use RelativeSource like this in SL4? I think it was improved in SL5?Ennis
Dan's correct, SL4 only does Self and TemplatedParent, AncestorType is a no go which is a shame because I run into instances where I could use it all the time. On that same route though I did however try Height={Binding ActualHeight, ElementName=ParentGridContainter} and had no luck either. I bound a textblock to the property and it returns a completely invalid number...but that's another topic lol.Quantifier
Yeah Im sorry AncestorType is new in SL5. Try to bind to Height and not ActualHeightMondragon
@inxs ya I tried both ways, neither did as expected. So to try and investigate a bit further I bound a textblock to both properties and they both returned the pixel size you might expect pre-populating (like 30 pixels) and would render a fraction of the height that it should, except once the views completely rendered it should have been reporting back more like 600+ so it's a quandry :/Quantifier
Are you sure you have the right containers for all? 30 pixels sounds like you may have the wrong layout or container?Mondragon
Ya you can grab a dependency real easy, name an object bind a textblock text to it like Text={Binding ActualHeight, ElementName=YourObjectName}Quantifier
K
0

I think if you're magically pulling in UI controls into your datatemplate, you're defeating the purpose of virtualization. Are these controls (grids) themselves being reused? What happens when the {Binding} changes? How much code is run in what looks like an attached behavior? Perhaps what you've done could be made performant. But if you just make a straight-forward datatemplate (or put in it an ordinary UserControl), you'll know all the controls in it will be reused and there will be minimum penalty in switching their datacontext.

I'm not sure about the issue with Height. It should work (get a finite height in Arrange), but it depends on what's it doing under the hood (perhaps it's trying to figure everything out before Arrange makes the height available).

You can always create your own virtualizing itemscontrol. Derive from Panel, put it inside a ScrollViewer, and implement IScrollInfo on the Panel. Then you'll know ALL the reasons why things are the way they are :)

Kipton answered 21/12, 2012 at 16:26 Comment(1)
You can also try using VirtualizingStackPanel directly inside a ScrollViewer. This may be a better way to force virtualization. You can also derive from VirtualizingStackPanel, which may give the advantages of deriving from Panel but with less work.Kipton
B
0

I'm not sure, if that would be an acceptable solution for your use case, but I've created a ContentControl named DelayedLayoutUpdateContainer that wraps a complex layout and helps improving performance. The idea behind the DelayedLayoutUpdateContainer is, that it adjusts the size of its content only when it has not been resized itself for a given time span. The size of the ContentPresenter inside the ControlTemplate is set to absolute values. So that may have the same effect as setting the ListBoxes height to an absolute value.

Botello answered 21/12, 2012 at 17:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.