WPF: The real difference between ContentControl and ContentPresenter

Most of the resources on the web specify that ContentPresenter supports a special property called ContentSource, with a default value of “Content“, which makes it easy for it to automatically set the values of these properties used to render content:

    • Content
    • ContentTemplate
    • ContentTemplateSelector
    • ContentStringFormat (3.5 sp1 +)

Basically, the property specifies the string prefix used to bind to properties of the parent. If you change the value of the ContentSource property to something else, like “Header“, the ContentPresenter’s properties would auto-bind to bind to these properties of the control you are templating using ControlTemplate:

    • Header
    • HeaderTemplate
    • HeaderTemplateSelector
    • HeaderStringFormat (3.5 sp1 +)

The web is siltent, however, about one major difference in behavior that’s important to note. 

ContentPresenter’s DataContext is automatically set to the value of its Content property, while ContentControl’s DataContext is not.

Why is it important?  In one word – bindings.  Bindings are resolved relatived to the value of the  DataContext property.  If you declare a binding on the ContentPresenter, the moment its content is set, the binding would be re-evaluated.  For example, let’s say you are building a dual-content control like this:

<UserControl x:Class="ContentPresenterVsContentControl.DualContentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ContentPresenter Grid.Row="0" 
                          Content="{Binding ContentOne}" 
                          ContentTemplate="{Binding ContentOneTemplate}" 
                          ContentTemplateSelector="{Binding ContentOneTemplateSelector}"/>
        <ContentPresenter Grid.Row="1" 
                          Content="{Binding ContentTwo}" 
                          ContentTemplate="{Binding ContentTwoTemplate}" 
                          ContentTemplateSelector="{Binding ContentTwoTemplateSelector}"/>
    </Grid>
</UserControl>

using System.Windows;
using System.Windows.Controls;

namespace ContentPresenterVsContentControl
{
    /// <summary>
    /// Interaction logic for DualContentControl.xaml
    /// </summary>
    public partial class DualContentControl : UserControl
    {
        public static readonly DependencyProperty ContentOneProperty = DependencyProperty.Register("ContentOne", typeof(object), typeof(DualContentControl));
        public static readonly DependencyProperty ContentOneTemplateProperty = DependencyProperty.Register("ContentOneTemplate", typeof(DataTemplate), typeof(DualContentControl));
        public static readonly DependencyProperty ContentOneTemplateSelectorProperty = DependencyProperty.Register("ContentOneTemplateSelector", typeof(DataTemplateSelector), typeof(DualContentControl));

        public static readonly DependencyProperty ContentTwoProperty = DependencyProperty.Register("ContentTwo", typeof(object), typeof(DualContentControl));
        public static readonly DependencyProperty ContentTwoTemplateProperty = DependencyProperty.Register("ContentTwoTemplate", typeof(DataTemplate), typeof(DualContentControl));
        public static readonly DependencyProperty ContentTwoTemplateSelectorProperty = DependencyProperty.Register("ContentTwoTemplateSelector", typeof(DataTemplateSelector), typeof(DualContentControl));


        public DualContentControl()
        {
            InitializeComponent();
            DataContext = this;
        }

        public object ContentOne
        {
            get { return GetValue(ContentOneProperty); }
            set { SetValue(ContentOneProperty, value); }
        }

        public DataTemplate ContentOneTemplate
        {
            get { return (DataTemplate) GetValue(ContentOneTemplateProperty); }
            set { SetValue(ContentOneTemplateProperty, value); }
        }


        public DataTemplateSelector ContentOneTemplateSelector
        {
            get { return (DataTemplateSelector) GetValue(ContentOneTemplateSelectorProperty); }
            set { SetValue(ContentOneTemplateSelectorProperty, value); }
        }

        public object ContentTwo
        {
            get { return GetValue(ContentTwoProperty); }
            set { SetValue(ContentTwoProperty, value); }
        }

        public DataTemplate ContentTwoTemplate
        {
            get { return (DataTemplate)GetValue(ContentTwoTemplateProperty); }
            set { SetValue(ContentTwoTemplateProperty, value); }
        }


        public DataTemplateSelector ContentTwoTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(ContentTwoTemplateSelectorProperty); }
            set { SetValue(ContentTwoTemplateSelectorProperty, value); }
        }

    }
}

Now, let’s use it in the project:

<Window x:Class="ContentPresenterVsContentControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContentPresenterVsContentControl"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <local:DualContentControl 
            ContentOne="Hello"
            ContentTwo="World">
            
            <local:DualContentControl.ContentOneTemplate>
                <DataTemplate>
                   <Label Background="Orange" Content="{Binding}" 
                          HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
                </DataTemplate>
            </local:DualContentControl.ContentOneTemplate>

            <local:DualContentControl.ContentTwoTemplate>
                <DataTemplate>
                    <Label Background="Green" Content="{Binding}" 
                           HorizontalContentAlignment="Center" VerticalContentAlignment="Center"/>
                </DataTemplate>
            </local:DualContentControl.ContentTwoTemplate>
        </local:DualContentControl>
    </Grid>
</Window>

You would expect something like this,image below, right?

DualContentControlWindow

Well, if you used ContentPresenters in your UserControl, you would see this, instead:

DualContentControlWindowBad

You would also see strange binding errors in the console

System.Windows.Data Error: 40 : BindingExpression path error: 'ContentTwoTemplate' 
property not found on 'object' ''String' (HashCode=-1506748533)'. 
BindingExpression:Path=ContentTwoTemplate; DataItem='String' (HashCode=-1506748533); 
target element is 'ContentPresenter' (Name=''); target property is 'ContentTemplate' (type 'DataTemplate')

System.Windows.Data Error: 40 : BindingExpression path error: 'ContentTwoTemplateSelector' 
property not found on 'object' ''String' (HashCode=-1506748533)'. BindingExpression:Path=ContentTwoTemplateSelector; DataItem='String' (HashCode=-1506748533); 
target element is 'ContentPresenter' (Name=''); target property is 'ContentTemplateSelector' (type 'DataTemplateSelector')

System.Windows.Data Error: 40 : BindingExpression path error: 'ContentOneTemplate' 
property not found on 'object' ''String' (HashCode=-694847)'. 
BindingExpression:Path=ContentOneTemplate; DataItem='String' (HashCode=-694847); 
target element is 'ContentPresenter' (Name=''); target property is 'ContentTemplate' (type 'DataTemplate')

System.Windows.Data Error: 40 : BindingExpression path error: 'ContentOneTemplateSelector' 
property not found on 'object' ''String' (HashCode=-694847)'. BindingExpression:Path=ContentOneTemplateSelector; DataItem='String' (HashCode=-694847); 
target element is 'ContentPresenter' (Name=''); target property is 'ContentTemplateSelector' (type 'DataTemplateSelector')

Notice that for some reason the ContentOneTemplate, ContentOneTemplateSelector, ContentTwoTemplate and ContentTwoTemplateSelector are bound to an object of type ‘String’. Why? Because the moment you sent the ContentProperty of the ContentPresenter its DataContext was switch to match the value of the Content property. Since the Content of the both ContentPresenters are string, “Hello” and “World” all other bindings on the ContentPresenter are now resolved against these string values!

If you replace ContentPresenters with ContentControls, the system works as expected.

<UserControl x:Class="ContentPresenterVsContentControl.DualContentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <ContentControl Grid.Row="0" 
                          Content="{Binding ContentOne}" 
                          ContentTemplate="{Binding ContentOneTemplate}" 
                          ContentTemplateSelector="{Binding ContentOneTemplateSelector}"/>
        <ContentControl Grid.Row="1" 
                          Content="{Binding ContentTwo}" 
                          ContentTemplate="{Binding ContentTwoTemplate}" 
                          ContentTemplateSelector="{Binding ContentTwoTemplateSelector}"/>
    </Grid>
</UserControl>
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s