How to Embed Arbitrary Content in a WPF Control

Jump to source code

Introduction

Many WPF controls (e.g. Button, Border, etc.) can display arbitrary XAML inside them:

<Button>any XAML here...</Button>

How do you I create my own control with this capability? So, if I write something like

<MyControl><TextBox Text="edit me" /></MyControl>

then a text box appears inside my control? This article will demonstrate how to do it the right way, as well as a couple of wrong ways and why they are wrong.

Naive Attempt: User Controls

My first attempt was to create a user control and try to add a ContentPresenter to it. Unfortunately, that did not work, and here's why.

I added a new user control to the project, and specified the contents in its XAML file:

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

Then I tried to use the control in my window like this:

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

Everything looks OK so far. Now I was told that ContentPresenter class can show user content in custom control, so I put it in the XAML definition for my user control:

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

It does not work, because we end up defining user control's Content property twice: once inside the user control XAML, and once inside Window XAML. Window XAML wins this battle, 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

It turns out that ContentPresenter works only inside a <ControlTemplate>. So, I created a QuickAndDirtyAttempt project with a Decorator control there (yes, I know, WPF already has a Decorator type, but I could not come up with a better name). To achieve our goals in a brute-force kind of way, I just assign a control template to the user control. Contrary to this MSDN article it is possible to apply a control template to a user control.

<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

Success! But we can do better than this.

Why Quick and Dirty Solution is Dirty

For starters, we don't really need a custom control here. We could just take an existing control such as ContentControl and supply a custom control template for it. This is the right solution in 80% of the cases.

WPF is all about separating "look and feel" from the presentation logic. WPF controls are said to be "lookless". 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. The system provides a default standard look for you, but if you want a checkbox with a round pushbutton and a text below it, all you need is to supply an alternate control template.

You will need a custom control if you want to handle additional logic, such as user input, bindable properties, and the like.

Below I will demonstrate both solutions: using a control template for ContentControl, and using a full fledged custom control.

Using ContentControl

We define our custom template 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 heaviest 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 the 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 Property. 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 application 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:

  • Quick-and-dirty option with UserControl and ControlTempalte.
  • Better option with ContentControl and ControlTemplate.
  • Heavy option with custom WPF 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.

Feedback

Questions? Comments? Feel free to
Leave feedback