WPF: Attached Property in XAML Markup (The object ‘…’ already has a child and cannot add ‘…’)

Let’s say you need an attached property to set an arbitrary header content to any DependencyObject, to let you use that value and populate the Header property of any HeaderedContentControl, creating the class directly in your WPF test application:

public static class HeaderManager
{
	public static readonly DependencyProperty HeaderProperty = DependencyProperty.RegisterAttached(
		"Header",
		typeof(object),
		typeof(HeaderManager),
		new PropertyMetadata(null));

	public static void SetHeader(DependencyObject element, object value)
	{
		element.SetValue(HeaderProperty, value);
	}

	public static object GetHeader(DependencyObject element)
	{
		return (object)element.GetValue(HeaderProperty);
	}
}

Then you want to attach that property to an UserControl that when injected in a TabItem can self declare it’s own header, and in first attempt you will end up trying to do this:

<UserControl x:Class="RadicalTabRegion.Presentation.FirstView"
             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"
             xmlns:s="clr-namespace:RadicalTabRegion.Presentation.Regions.Specialized"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">

    <s:HeaderManager.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="First View"/>
            <CheckBox/>
        </StackPanel>
    </s:HeaderManager.Header>

    <Grid>
        <TextBlock Text="I'm the first view"/>
    </Grid>

</UserControl>

But if you try to compile this, you will get this error:

“The object ‘UserControl’ already has a child and cannot add ‘Grid’. ‘UserControl’ can accept only one child.”

This is because the compiler needs to know that HeaderManager.Header is an attached property, before compile xaml in baml, but because the HeaderManager class is declared in the same assembly of the xaml, it can’t. Actually I think this can be overcome by Microsoft, but never mind there’s a couple of solutions for that.

The first solution is to move the markup declaration after the first element in the UserControl content, and our case after the Grid:

<UserControl x:Class="RadicalTabRegion.Presentation.FirstView"
             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"
             xmlns:s="clr-namespace:RadicalTabRegion.Presentation.Regions.Specialized"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">

    <Grid>
        <TextBlock Text="I'm the first view"/>
    </Grid>

    <s:HeaderManager.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="First View"/>
            <CheckBox/>
        </StackPanel>
    </s:HeaderManager.Header>

</UserControl>

The second solution is to move the HeaderManager class into a different class library, so in a different assembly, and this is the best approach because let you use the attached property more naturally, without incurring in that compile error even if you declare the property just before the UserControl.Content, that is more natural for an Header property. For instance if you add the class into a class library called “RadicalTabRegion.Windows.Presentation” you will end-up with this XAML that just works fine:

<UserControl x:Class="RadicalTabRegion.Presentation.FirstView"
             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"
             xmlns:s="clr-namespace:RadicalTabRegion.Windows.Presentation.Regions.Specialized;assembly=RadicalTabRegion.Windows.Presentation"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">

    <s:HeaderManager.Header>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="First View"/>
            <CheckBox/>
        </StackPanel>
    </s:HeaderManager.Header>

    <Grid>
        <TextBlock Text="I'm the first view"/>
    </Grid>

</UserControl>

This example is a part of an implementation I’m making for Radical to implement a TabControlRegion, that is able to inject a simple UserControl into a TabItem (generated at runtime) and let the developer deeply customize the TabItem.Header simply declaring the attached property directly into the UserControl. This is under development right now, and it is just one of the possible solutions, if I will like it, I will post the entire code, and pull a request for integrating the new region directly into Radical.

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