Being able to visualize large sets of data affords one the ability to process and manipulate it in ways previously not possible. When dealing with the real world, these data sets will easily end up being extremely large. Therefore, proper and efficient handling of the act of representing these large data sets with visual controls is a necessity.

If you are developing anything of significance using WPF, then there’s a very good chance you are using the MVVM pattern. If you are using the MVVM pattern, then you are most likely notifying binding clients through INotifyPropertyChanged. Now, when we’re dealing with collections of bound data, INotifyPropertyChanged will only get you so far; you will most likely be using ObservableCollection (or some similar variant) populated with items that implement INotifyPropertyChanged. This only makes sense; this allows you to fully support transferring collection data values from binding source objects to binding targets.

One may incorrectly assume that using an ObservableCollection populated with items all implementing INotifyPropertyChanged results in a longer binding time than using an ObservableCollection populated with normal CLR objects. In actuality, the opposite is true. Even though the items implementing INotifyPropertyChanged exhibit additional functionality in comparison to their standard counterparts, they also allow the WPF data binding engine to use a slightly more efficient process in the effort to resolve the items’ object references. Optimizing data binding performance, however, is outside the scope of this article. Instead, we are going to focus solely on the performance cost of what happens after object references are resolved.

So, why is it that large data sets result in such a performance hit when binding them to ItemsControl? The data set does not even need to be that large, if the resulting child view appearing in the ItemsControl is of sufficient complexity. The reason is simple: when you bind an ObservableCollection to an ItemsControl, a layout container is generated for each item associated with the collection. This requires calculating the size and position of each of these containers, among other things. So, if you are working with thousands (or even hundreds, or less in some cases), the generation of all of these containers is going to be VERY NOTICEABLE.

There are a number of strategies that can be employed when dealing with problems like this. The common idea among all of them, isvirtualization. The kind of virtualization I’m going to cover here is UI virtualization. The goal of UI virtualization is to delay layout computation for an item until the item is to become visible to the user. Another important aspect of dealing with large amounts of data is the use of data virtualization, which some readers may be more familiar with. Data virtualization achieves the same thing with the data itself; only data that would be visible to the user is stored in memory. That is also another topic, and not the focus of this article. Additionally, WPF still lacks built-in support for data virtualization as of 4.0 (there are many articles on the topic, however, using custom made solutions).

So how do we achieve UI virtualization with our ItemsControl? Well, there’s one easy way: Don’t use ItemsControl! Use a derivative control instead; namely, ListView and ListBox controls, which have UI virtualization enabled by default. Note that those are the only ItemsControl derivatives that have virtualization enabled by default, and many other ones don’t even support virtualization. If your product’s requirements can be satisfied by one of the two previously mentioned controls, then, by all means, use them! However, advanced scenarios require the use of ItemsControl. What these scenarios are, are not the point of this article either. Idle prattling aside, how do we get virtualization going with ItemsControl?

Before we look at how to get virtualization going, we may want to look at how NOT to get virtualization going. If any of the following conditions are true, UI virtualization will be disabled (no matter what you try to do):

  • You make use of item groupings. You are guilty of this if you are using a CollectionViewSource with GroupDescriptions defined.
  • Setting the panel’s CanContentScroll to false. Scrolling has to be allowed, otherwise, how would it know which items should have their layout computation deferred?
  • You add item containers directly to the ItemsControl (such as ListBoxItem objects, etc.) If you are not guilty of any of the heinous crimes listed above, then you should be able to use UI virtualization without fear of it being disabled. Ok, now the goods.

The magic word we’re looking for here is VirtualizingStackPanel.

The VirtualizingStackPanel has two attached properties available for use by the ItemsControl class. Namely,VirtualizingStackPanel.IsVirtualizing and VirtualizingStackPanel.VirtualizationMode.

IsVirtualizing should be straight forward. Unless the idea of UI virtualization disturbs your very soul, set it to true. What about the mode? Well, we have two options here: Standard, and Recycling.

With virtualization enabled, when an item container goes off screen, it will end up getting destroyed. Naturally this means the container needs to be created once again should it come into view. This self-destructive act, of course, comes at a cost. To reduce this cost, the use of Recycling allows the use of the same item containers for different items.

If using the Recycling mode is possible, you will want to use that; successful use of Recycling mode will result in better performance than using the Normal virtualization mode. Unfortunately, from my experience, if the child views being rendered are sophisticated in any degree, you are going to find that Recycling mode does not work for you. If your child views have multiple states, you may need to save that state information and then revert the child back to its default state if Recycling mode is enabled. Failure to do this will result in the non-default state information to carry over to unrelated items, which are expected to be in a default state upon first contact. In addition to that, using multiple kinds of item containers will directly disable container recycling, and revert basically to a Normal mode of virtualization.

Now, getting back to the business at hand, if you run with what we have so far, you may notice no difference. This is because we need to change the default ItemsPanelTemplate. I ended up with the following:

    <ItemsControl
       ItemsSource="{Binding ChildViewModels}"
       VirtualizingStackPanel.IsVirtualizing="true"
       VirtualizingStackPanel.VirtualizationMode="Standard"
       .
       .
       >
       <ItemsControl.Template>
          <ControlTemplate TargetType="{x:Type ItemsControl}">
             <ScrollViewer CanContentScroll="false">
                <ItemsPresenter/>
             </ScrollViewer>
          </ControlTemplate>
       </ItemsControl.Template>
       <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
             <VirtualizingStackPanel Orientation="Vertical" IsItemsHost="True"/>
          </ItemsPanelTemplate>
       </ItemsControl.ItemsPanel>
    </ItemsControl>

I found that setting the Scrollviewer’s CanContentScroll property to false in the ItemsControl’s ControlTemplate didn’t disable the UI virtualization. Perhaps it only comes into play when defined somewhere on the panel?

After getting virtualization to actually happen, the performance increase was phenomenal.

Matt Weber

I'm the the Senior Software Architect at Emergingsoft where I lead the software development team. I am also the owner of this website. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.