DCSIMG
WPF Performance Sweets – ContentControl.Content = null - Essential XAML

WPF Performance Sweets – ContentControl.Content = null

Part 2
Background

Yesterday I came back from my customers’ place. He had performance issues with his WPF composite application. Each time he navigates away from a page, he pays about 1400ms! – Playing around we’d found that each time a page was changed, the composite framework he uses replaces the old page with the new page by simply updating the ContentControl.Content (placeholder) with the new page. This operation solely took about 800ms!

placeholder.Content = newPage;

In this post I would like to talk about:

- Performance problems you may encounter by using ContentControl incorrectly or not using it by the book.

- How to intercept them.

- How to solve them using ContentControl best practices.

First I want to talk about well-known techniques for displaying views in a composite application, using Prism or other libraries such as CAB/SCSF and MEF.

Working with patterns such as MVP, Presentation Model or MVVM, the view is separated from the logic by having at least two parts: View and Presenter. The view is usually injected/composed into the composite area (Region in Prism, Zone in SCSF and Import in MEF) directly or indirectly, where the place holder (or view host) is usually a ContentControl.

By saying directly, the ContentControl representing the Region/Zone is initialized with the view by setting the Content property with the instance of the view, where instance of the view is usually a UserControl or other WPF element.

By saying indirectly, the ContentControl is initialized with the view by setting the Content property with the instance of the presenter, where instance of the presenter is a simple CLR class (sometimes DependencyObject), and the view is a side-effect of the DataTemplate provided with the presenter (usually merged with the application or shell/window resources).

IMHO the second approach (indirectly) is much better, and there are several reasons for that. The most persuading reason is: performance.

So how performance is related to the ContentControl.Content issue?

This is exactly what I want to talk about. Let me start with a little demonstration.

The shell presenting the view using a ContentControl
<Window x:Class="ContentControlSweets.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Shell" Height="300" Width="300">

    <Grid>
        <TabControl>

            <TabItem Header="View">
                <ContentControl x:Name="_placeholder"
                                x:FieldModifier="private"
                                Margin="16"
                                HorizontalContentAlignment="Center"
                                VerticalContentAlignment="Center" />
            </TabItem>

            <TabItem Header="Other View"
                     Content="Other View Content"
                     HorizontalContentAlignment="Center"
                     VerticalContentAlignment="Center" />

        </TabControl>
    </Grid>

</Window>

public partial class Shell : Window

{

    public Shell()

    {

        InitializeComponent();

    }

    public object Placeholder

    {

        set { _placeholder.Content = value; }

    }

}

The presenter

public class Presenter

{

    public string Description { get; set; }

}

Direct view
DirectView.xaml
<UserControl x:Class="ContentControlSweets.DirectView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <Grid>
        <TextBlock Text="{Binding Description}"
                   FontSize="24"
                   Foreground="Green"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" />
    </Grid>
    
</UserControl>
DirectView.cs

public partial class DirectView : UserControl

{

    public DirectView()

    {

        InitializeComponent();

    }

}

Direct view loading

private void ButtonDirect_Click(object sender, RoutedEventArgs e)

{

    new Shell()

    {

        Placeholder = new DirectView()

        {

            DataContext = new Presenter()

            {

                Description = "Direct View"

            }

        }

    }.ShowDialog();

}

Indirect view
IndirectView.xaml

 

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ContentControlSweets">

    <DataTemplate DataType="{x:Type local:Presenter}">
        <Grid>
            <TextBlock Text="{Binding Description}"
                       FontSize="24"
                       Foreground="Blue"
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center" />
        </Grid>
    </DataTemplate>

</ResourceDictionary>

 

 
Indirect view loading

private void ButtonIndirect_Click(object sender, RoutedEventArgs e)

{

    var shell = new Shell()

    {

        Placeholder = new Presenter()

        {

            Description = "Indirect View"

        }

    };

    // This can be done from XAML.

    shell.Resources.MergedDictionaries.Add(

        new ResourceDictionary()

        {

            Source = new Uri("IndirectView.xaml", UriKind.Relative)

        });

    shell.ShowDialog();

}

Performance test

Measuring the unload time of both techniques described above (using my machine), by setting the ContentControl.Content property with null, here are the results: direct-view 0ms, indirect-view 0ms.

As you may guess, it’s exactly the same!

Well, lets try to mimic a more realistic application by adding more controls to both views.

After adding 100 buttons and 100 text blocks to both views, and measuring the unload scenario again, here are the new results: direct-view 15ms, indirect-view 0ms.

After adding another 100 buttons and 100 text blocks to both views, and measuring the unload scenario again, here are the new results: direct-view 30ms, indirect-view 0ms.

Wow! It looks like working with direct views, the bigger the logical-tree is, the more time it takes to unload the view. Working with an indirect view takes ZERO time to unload the view.

How’s that?

The best way to know what’s happening there is to take a closer look at the ContentControl using Reflector.

Lets start by looking at the ContentControl.OnContentChanged method. This method is activated each time the ContentControl.Content property changes with a different value (in our case, setting the content to null, or just changing views).

 

 

protected virtual void OnContentChanged(object oldContent, object newContent)
{
    base.RemoveLogicalChild(oldContent);
    if (!this.ContentIsNotLogical)
    {
        if (base.TemplatedParent != null)
        {
            DependencyObject current = newContent as DependencyObject;
            if ((current != null) && (LogicalTreeHelper.GetParent(current) != null))
            {
                return;
            }
        }
        base.AddLogicalChild(newContent);
    }
}

Commenting the RemoveLogicalChild method call significantly reduces the unloading time!

Lets take a closer look at this method.

protected internal void RemoveLogicalChild(object child)
{
    if (child != null)
    {
        if (this.IsLogicalChildrenIterationInProgress)
        {
            throw new InvalidOperationException(SR.Get("CannotModifyLogicalChildrenDuringTreeWalk"));
        }
        FrameworkObject obj2 = new FrameworkObject(child as DependencyObject);
        if (obj2.Parent == this)
        {
            obj2.ChangeLogicalParent(null);
        }
        IEnumerator logicalChildren = this.LogicalChildren;
        if (logicalChildren == null)
        {
            this.HasLogicalChildren = false;
        }
        else
        {
            this.HasLogicalChildren = logicalChildren.MoveNext();
        }
    }
}

Keep digging inside, you’ll find that ContentControl recursively changes the logical parent of each element all the way down the tree. Having a very simple content such as our Presenter, this won’t happen, since in such case we don’t have a tree.

Conclusion

The best way to work with WPF ContentControl is to set its Content property with a simple CLR object (can be DependencyObject), which is not part of a logical tree, having a DataTemplate for displaying it.

Resources

You can download my test application from here.

Feel free to register my blog RSS feed to see more WPF performance sweets.

 

Published Friday, September 11, 2009 8:34 AM by Tomer Shamam
תגים:, , ,

Comments

# re: WPF Performance Sweets – ContentControl.Content = null

Saturday, September 12, 2009 10:31 PM by rantri

Nice, interesting.

Miss you :)

# re: WPF Performance Sweets – ContentControl.Content = null

Monday, September 14, 2009 10:24 PM by Rob

Hello! I couldn't help wondering if your client's framework was Caliburn.  I was not aware of the issues that you mentioned when I wrote some of Caliburn's DefaultViewStrategy implementation and had not experienced them nor had anyone mention them on the forum.  My apologies if it was my framework and it caused some pain.  Either way, I'm appreciative of your post and am going to use your findings to improve Caliburn's performance in the future.  I was wondering if you could provide some more details about the application architecture.  Such as: is it an MDI style app, a Navigation app, etc.  I am also curious just how complex the screen are and was wondering if changing the behavior did indeed fix his problem in the end.  I wanted to confirm that there wasn't something else in the mix, such as sync web service calls.  1400ms seams like an awfully long time even for a complex screen.  (As a side note, if it was indeed Caliburn, I would love to have any additional feedback on the framework you or your client are willing to provide.) Thanks!  You are helping me to improve my framework!

# re: WPF Performance Sweets – ContentControl.Content = null

Tuesday, September 15, 2009 9:14 PM by Tomer Shamam

Hi Rob,

My client uses CAB/SCSF, and the problem I described caused by navigating from one 'page' to another, using a simple ContentControl as the navigation zone, where each 'page' is a WPF UserControl.

The page is kind of complex, actually it contains many 3rd party controls on it.

So far I know that this solution fixed the problem but now the problem moved to other place, so tomorrow I'll figure out where and why.

Thanks for your concern.

# re: WPF Performance Sweets – ContentControl.Content = null

Tuesday, September 15, 2009 10:08 PM by Tomer Shamam

Hey Ran,

Somehow your comment was filtered as spam.

Thanks, miss you too :).

# re: WPF Performance Sweets – ContentControl.Content = null

Thursday, June 23, 2011 10:21 AM by Scott C

Ive tried using this solution to the slow content swapping performance - and while it DOES increase page swapping performance my UI also sticks around ... which makes sense as its still in the logical tree. Any thoughts on this?

# re: WPF Performance Sweets – ContentControl.Content = null

Wednesday, January 11, 2012 9:30 AM by Per Lundberg

Very interesting blog post indeed. With huge visual trees, the performance impact of setting ContentControl.Content = null can be HUGE. We are talking about times like 10+ seconds! However, I don't know if the "indirect view" approach will work in our case, but still, your thoughts and explanations make a bit of sense. Will try to find a workaround based on this.

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Enter the numbers above:
Powered by Community Server (Commercial Edition), by Telligent Systems