How to Embed Arbitrary Content in a WPF Control

Jump to source code

Introduction

"Upgrading" simple XAML fragments to user controls is easy. You add a new user control to the project, specify the contents in the control's XAML file:

<UserControl x:Class="MyNamespace.MyControl" ... >
    <StackPanel Orientation="Vertical">
        <Label>Foo</Label>        
        <Label>Bar</Label>
    </StackPanel>
</UserControl>

Then you can use the control in your window or page by specifying its tag:

<Window xmlns:local="clr-namespace:MyNamespace" ...>
    <local:MyControl />
</Window>

Naive Attempt

However, what if you wanted to add user supplied content to the control? Something like a button that can contain arbitrary XAML? My first attempt was utterly naive and failed miserably:

<!-- don't do this at home --> 
<UserControl x:Class="MyNamespace.MyControl" ... >
    <StackPanel Orientation="Vertical">
        <Label>Foo</Label>        
        <ContentPresenter />
        <Label>Bar</Label>
    </StackPanel>
</UserControl>
...
<Window xmlns:local="clr-namespace:MyNamespace" ...>
    <local:MyControl>User supplied content here</local:MyControl>
</Window>

Simply put, this does not work. We end up defining user control's Content property twice: once inside the user control XAML, and once inside Window XAML. In this case, Window XAML wins, and the only thing you will see is the text User supplied content here. Neither Foo, nor Bar are shown, and ContentPresenter has no effect.

Quick and Dirty Attempt

ContentPresenter works only inside a <ControlTemplate>. So, to achieve our goals in a brute-force kind of way, we can just assign a control template to our user control. WPF seems to have no problem applying control template to a UserControl, despite an MSDN article claiming it is not possible.

<UserControl x:Class="QuickAndDirtyAttempt.Decorator" ...
    <UserControl.Template>
        <ControlTemplate TargetType="{x:Type local:Decorator}">
            <StackPanel Orientation="Vertical">
                <Label>Foo</Label>
                <ContentPresenter />
                <Label>Bar</Label>
            </StackPanel>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>
Note the TargetType property on the template: without it the project will happily compile, but the ContentPresenter will not work.
<Window ... >
    <StackPanel Orientation="Vertical">
        <local:Decorator>
            <Label Background="Wheat">User supplied content here</Label>
        </local:Decorator>
    </StackPanel>
</Window>
Screenshot of the quick and dirty attempt

Why Quick and Dirty Solution is Dirty

There are several problems with the quick and dirty solution. We all know about the separation between the UI and the business logic. WPF takes this one step further - it strictly suggests separating presentation logic and actual rendering. This may sound odd at first, but it does bring enormous flexibility.

WPF controls are said to be "look-less". For example, the checkbox control only handles checkbox state (on, off, undefined), and transitions between those states. It does not define the look of the check box, or where the checkbox labels are rendered. This is a job of the control template. Default control template shows familiar square on the left and the rest of the content on the right, but users can define their own control templates where checkbox looks nothing like a square.

Our case has absolutely no presentation logic, so we don't really need a custom control in the first place. We can accomplish the same goal by taking a simple control (e.g. ContentControl and supplying a template for it. This is a solution to 80% of your GUI problems - WPF may already have a control that does the job, you just need to supply a new template. If we did have some custom presentation logic, we would need to create our own custom control, and supply a default template for it. I will demonstrate both solutions.

Using ContentControl

We define our custom tempalte in application resources:

<Application ...>
    <Application.Resources>
        <ControlTemplate x:Key="Decorator" TargetType="ContentControl">
            <StackPanel Orientation="Vertical" >
                <Label>Foo</Label>
                <ContentPresenter />
                <Label>Bar</Label>
            </StackPanel>
        </ControlTemplate>
    </Application.Resources>
</Application>

Then we use ContentControl in our window, specifying the template as follows:

<Window ... >
    <StackPanel Orientation="Vertical">
        <ContentControl Template="{StaticResource Decorator}">
            <Label Background="Yellow">User supplied content here</Label>
        </ContentControl>
    </StackPanel>
</Window>

I do wish the template name syntax were less verbose, but I don't think it can be simplified here. If we want to specify other properties of the control together with the template, we may define a custom control style instead of setting the template directly. The style would then have a setter for the template property. However, I believe that in this particular case it would be an unnecessary complication.

Screenshot of using ContentControl

Using Custom Control

Custom control is the haviest way to customize WPF, and it should be used only when necessary. If you think you need a custom control, first try to find an existing control that satisfies your needs. Remember that WPF controls are pure logic plus a default look-and-feel template (<ControlTempalte> in WPF speak). This template can be overhauled dramatically, as shown in this Charles Petzold's article.

If your customizations go beyond look and feel and involve some presentation logic, custom control is probably a good answer. But even then it may a good idea to derive your control from one of existing controls such as ContentControl. Typically, your custom control(s) will live in their own assembly, and this assembly will be referenced by user applications.

We are going to create a control called HeaderFooterControl, which defines two custom properties: Header and Footer. You would use Decorator as follows:

<my:HeaderFooterControl>
    <my:HeaderFooterControl.Header>
        header content here
    </my:HeaderFooterControl.Header>
    <my:HeaderFooterControl.Footer>
        footer content here
    </my:HeaderFooterControl.Footer>
    main control content here
</my:HeaderFooterControl>

If you right click on your project, and choose Add → "Add New Item" → WPF → "Custom Control (WPF)" Visual Studio generates the boilerplate for your control. This includes the skeleton .cs file and default control template in generic.xaml.

Adding custom WPF control in Visual Studio

By default your control is derived from WPF Control class, but we can do better by inheriting HeaderedContentControl. It has everything we need except for the footer. Generally, you should attempt deriving your control from the most appropriate basic WPF class (the diagram is taken from here):

WPF custom control hierarchy

Next step is to define a dependency property for Footer: the shortcut for that is right click inside your .cs file, then Insert snippet → NetFX3d → Dependency Proeprty. The property is of type object, because the footer can contain any user-defined content.

public static readonly DependencyProperty FooterProperty =
   DependencyProperty.Register("Footer", typeof(object), typeof(HeaderFooterControl), new UIPropertyMetadata(null));

Then we define our control template in the Generic.xaml. We use ContentPresenter to show arbitrary user content from header, footer, and main control body:

<Style TargetType="{x:Type local:HeaderFooterControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:HeaderFooterControl}">
                <StackPanel Orientation="Vertical">
                    <ContentPresenter ContentSource="Header" />
                    <ContentPresenter />
                    <ContentPresenter ContentSource="Footer" />
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

We can also map our C# namespace to an XML namespace by applying the following attribute to the controls assembly:

[assembly:XmlnsDefinition("http://ikriv.com/xaml/samples/DisplayingContent/CustomControl/MyControls", "MyControls")]

Now, in the user application we can reference our control library and say in Window1.xaml:

<Window xmlns:my="http://ikriv.com/xaml/samples/DisplayingContent/CustomControl/MyControls" ...>
    <my:HeaderFooterControl>...&lgt;/my:HeaderFooterControl>
</Window>

Here is the screenshot of our aplication using custom control:

Screenshot of using custom control

What Option to Choose

I have described three different options for embedding user-defined content in your control:

You rarely want to use the quick-and-dirty option - maybe only as a temporary measure, but remember that temporary measures of today tend to become the legacy code of tomorrow. Use ContentControl if all you need is to display some stuff. Use custom control if you have your own presentation logic. For instance, you may want to show/hide header or footer by setting HeaderVisible and FooterVisibleproperties, and use these properties in some triggers.

Source Code

A Visual Studio 2008 solution with all relevant source code can be downloaded here. The code is Copyright (c) Ivan Krivyakov. It is distributed under the Apache License.