« CommandTarget, MenuItem, ContextMenus, and Binding, oh my! | Main | Separated at birth? JavaFX and WPF/XAML? »

Disposing Virtualizing Stack Panel

A VirtualizingStackPanel is very useful when using WPF -- when items scroll out of the viewable area, they may be destroyed to reclaim the valuable UI resources they are consuming. By default, the VirtualizingStackPanel automatically destroys the virtual tree for items that become reclaimed automatically, without the item being aware at all. Often, items are mapped to a DataTemplate which provide the UI.

I was in need of a way to have the virtualized item know when it was being reclaimed -- as the internals of the object held onto resources that could have been considered wasteful if they were to pointlessly be held (in this case, it was a bitmap). Using a simple overridden method of the VirtualizingStackPanel, OnCleanUpVirtualizedItem, it's easy to inform the displayed item when its visuals are being removed.

  public class VirtualizingStackPanelEnh : VirtualizingStackPanel
  {
    protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs e)
    {
      IDisposable disp = e.Value as IDisposable;
      base.OnCleanUpVirtualizedItem(e);
      if (disp != null)
      {
        disp.Dispose();
      }
    }
  }

Above is my simple implementation. Items which want to know they no longer are being shown can implement IDisposable. When the Dispose method is called, clean up any internal resources that may be wasteful if the item is not being displayed. Of course, your items don't have to implement IDisposable, it could be anything you want, I have an implementation that just calls a "Cleanup" method on a known object as well. (It could be an event that is raised to the list item as well).

    <Style x:Key="PhotoListBoxStyle" TargetType="{x:Type ListBox}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type ListBox}">
            <Border Background="{TemplateBinding Panel.Background}"
                BorderBrush="{TemplateBinding Border.BorderBrush}"
                BorderThickness="{TemplateBinding Border.BorderThickness}">
              <ScrollViewer HorizontalScrollBarVisibility="Auto" Template="{DynamicResource ScrollViewerControlTemplate1}">
                <local:VirtualizingStackPanelEnh IsItemsHost="True" Orientation="Horizontal"/>
              </ScrollViewer>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

To use the new stack panel, I created a new ControlTemplate for a ListBox and embedded the VirtualizingStackPanelEnh directly inside the ScrollViewer.

Comments

Hi,

I was just reading your code on the PhotoScroll app and found this piece of code which made wonder... : which is the inverted event ? In fact, your code permits to know when an item is no more on-screen in order to destroy it, but how do I know when the item should be redrawn ?

I've some Borders which I fill with Freezed ImageBrushes. When out-of-screen, I assign the background of a to null, but how do I know when I must re-fill this background ?

Thanks

The ItemContainerGenerator, a feature of ItemPanels, is automatically created when needed by the VirtualizingScrollPanel that is used within the code. So, there’s really no special logic that needs to happen to make that work. In my code, from what I recall, the request for a Thumbnail is received, which triggers the code to reload the image/thumbnail. So, any request that comes in is verified – if null, then I’ll need to load it. If the internal image is already set, nothing to do but return it. So, when it gets destroyed, the code cleans up and sets that variable to null, like you suggested.

Hope that helps.

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).