Click here to Skip to main content
15,860,861 members
Articles / Desktop Programming / WPF

Wrap Panel Virtualization

Rate me:
Please Sign up or sign in to vote.
4.95/5 (18 votes)
2 Jan 2012CPOL2 min read 52.6K   5.6K   41   9
WrapPanel doesn't support virtualization. But we can improve the performance by simulating virtualization.

Introduction

While developing Media Assistant, I faced several issues, mostly with the performance. I have written another article titled Replacing TreeView with ListBox, where I explained how I simulated a listbox to look like a tree view to support virtualization. I had to do that because a tree view performs very, very slow if there are about 20,000 nodes. I faced a similar issue when I added a Thumbnail View to display the movies in the repository. I used a ListBox where I changed the ItemsPanel to WrapPanel. When I use the WrapPanel, the ListBox loses its virtualization ability and it takes a lot of time to display 5000 movies in my repository. Because WrapPanel doesn't support virtualization. In this article, I will explain how I virtualized the WrapPanel.

Thumbnail View

Image 1

ListBox with WrapPanel

I changed the ItemsPanel of ListBox with WrapPanel to show the thumbnail view.

XML
<ListBox x:Name="thumbnailListBox" ItemsSource="{Binding DataSource.ResultMovies}" 
          ScrollViewer.HorizontalScrollBarVisibility="Disabled"
          SelectedItem="{Binding DataSource.SelectedMovie}"
          ScrollViewer.IsDeferredScrollingEnabled="True"
          ItemTemplate="{StaticResource NormalThumbnailTemplate}"
          ScrollViewer.ScrollChanged="HandleScrollChanged"
          >
    <ListBox.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Play" Command="{Binding DataSource.PlayMovieCommand}"/>
            <Separator/>
            <MenuItem Header="Show in Windows Explorer" 
               Command="{Binding DataSource.ShowMovieInWindowsExplorerCommand}"/>
        </ContextMenu>
    </ListBox.ContextMenu>
            
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

Once I used the WrapPanel with ListBox, it displays the movies in a thumbnail like view without virtualization. I could not find any solution to virtualize the WrapPanel. So, I investigated which part of my code was taking time. Then I found that individual thumbnail items take some time to render because I used an image, there was some processing with each item, and I was using the converters, etc.

I tried with a simple border without anything in it with WrapPanel and it displays very quickly. So, I tried to use the detail movie view with an image and other information only for the items which are in the visible area, and all other items which are not in the visible area with a simple border. I kept changing the views while the user scrolled. I subscribed to the ScrollChanged event and used an ItemTemplate which switches between a normal border and a detail movie view.

Thumbnail Template

XML
<DataTemplate  x:Key="NormalThumbnailTemplate">
    <Border Width="300" Height="200" BorderThickness="1" Margin="5" BorderBrush="Gray">
        <ContentControl x:Name="content" Content="{Binding}" ContentTemplate="{x:Null}"/>
    </Border>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding IsVisible}" Value="True">
            <Setter TargetName="content" Value="{StaticResource NormalThumbnail}" Property="ContentTemplate"/>
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

In the movie data, I used a property IsVisible which is used in the DataTrigger to switch to the thumbnail view. NormalThumbnail is the DataTemplate which displays the detail movie information with image and other data. So I'm not going to explain that part in this article.

How to identify which items are in the visible area of the ListBox

C#
private void HandleScrollChanged(object sender, ScrollChangedEventArgs e)
{
    ShowVisibleItems(sender);
}

private void ShowVisibleItems(object sender)
{
    var scrollViewer = (FrameworkElement) sender;
    var visibleAreaEntered = false;
    var visibleAreaLeft = false;
    var invisibleItemDisplayed = 0;
    foreach (Movie item in thumbnailListBox.Items)
    {
        if (item.IsVisible) continue;
        var listBoxItem = 
          (FrameworkElement) thumbnailListBox.ItemContainerGenerator.ContainerFromItem(item);
        if (visibleAreaLeft==false && 
            IsFullyOrPartiallyVisible(listBoxItem, scrollViewer))
        {
            visibleAreaEntered = true;
        }
        else if (visibleAreaEntered)
        {
            visibleAreaLeft = true;
        }
        if(visibleAreaEntered)
        {
            if(visibleAreaLeft && ++invisibleItemDisplayed>10)
                break;
            var job = new Job(MakeVisible);
            job.Store.Add(item);
            job.Start();
        }
    }
}

This code simply finds those items which are fully or partially visible and marks them as visible. I used a Job class which marks the items to be visible in a different thread. I did it just to delay the trigger. But it is not necessary in most cases. So, ignore that part.

C#
protected bool IsFullyOrPartiallyVisible(FrameworkElement child, FrameworkElement scrollViewer)
{
    var childTransform = child.TransformToAncestor(scrollViewer);
    var childRectangle = childTransform.TransformBounds(
                              new Rect(new Point(0, 0), child.RenderSize));
    var ownerRectangle = new Rect(new Point(0, 0), scrollViewer.RenderSize);
    return ownerRectangle.IntersectsWith(childRectangle);
} 

This code identifies if an item is in the visible area of the scroll viewer. Then I find the visible items rectangle and the view point's rectangle in the same co-ordinate system and find the intersection. And I'm done.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) KAZ Software Limited
Bangladesh Bangladesh
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionFor cases where cannot TransformToAnscestor Pin
Yotam Shippin7-Apr-16 5:38
Yotam Shippin7-Apr-16 5:38 
QuestionMy vote of 5 Pin
Member 1028177217-Nov-14 3:16
Member 1028177217-Nov-14 3:16 
QuestionMy vote is 5 Pin
WuRunZhe24-Jun-14 1:21
WuRunZhe24-Jun-14 1:21 
GeneralMy vote of 5 Pin
Monjurul Habib2-Jan-12 20:22
professionalMonjurul Habib2-Jan-12 20:22 
GeneralMy vote of 5 Pin
ring_02-Jan-12 20:07
ring_02-Jan-12 20:07 
Awesome. Thank you for sharing.
QuestionActually there IS a VirtualizingWrapPanel Pin
Begench Amanov2-Jan-12 11:31
Begench Amanov2-Jan-12 11:31 
GeneralMy vote of 5 Pin
Saraf Talukder2-Jan-12 7:45
Saraf Talukder2-Jan-12 7:45 
GeneralMy vote of 5 Pin
Topu2-Jan-12 7:30
Topu2-Jan-12 7:30 
GeneralMy vote of 5 Pin
Rahman Masudur2-Jan-12 4:54
Rahman Masudur2-Jan-12 4:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.