❀°。Deijin's Blog

How To Create A ContentControl With Multiple Children In WPF

Consider if you can use simpler options:

  1. If you want to have a single child, then you can inherit from ContentControl the same way wpf controls such as the Button do.
  2. If you want one main child, and one "header" child, then you can inherit from HeaderedContentControl the same way wpf controls such as the Expander do.

You only want to do this if you need to have two or more child controls, and the HeaderedContentControl isn't suitable. If so then let's get started.

1. Create a CustomControl

The first step is to create a new custom control, if you use the visual studio template by default these inherit from Control which is what we want. It will also create the default style for your control within the file Themes/Generic.xml.

// Newly created control
public class MultipleContentControl : Control
{
    static MultipleContentControl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MultipleContentControl), new FrameworkPropertyMetadata(typeof(MultipleContentControl)));
    }
}
<!-- Defualt style that was created for the control -->
<Style TargetType="{x:Type local:MultipleContentControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MultipleContentControl}">
                <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

2. Add Properties and ContentPresenters

Next add a dependency property for each child content you want to add. And in the xaml ControlTemplate you can add a ContentPresenter with its ContentSource set to the property name.

// Dependency property registration
public static readonly DependencyProperty LeftContentProperty = DependencyProperty.Register(
    nameof(LeftContent),
    typeof(object),
    typeof(MultipleContentControl)
    );

// Property accessor
public object LeftContent
{
    get => GetValue(LeftContentProperty);
    set => SetValue(LeftContentProperty, value);
}
<!-- Add this xaml somewhere in the ControlTemplate. 
Optionally bind to content alignment properties -->
<ContentPresenter 
    ContentSource="LeftContent" 
    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
    />

The ContentSource property is magic and will also locate various other properties named appropriately, so if you want it to be an option for any of yours, you can add one or all of the following properties:

You may also want to add an attribute on the class to indicate which content is the "main" content. This is the one that you can use in xaml as a direct child element rather than having to specify the property name.

[ContentProperty(nameof(LeftContent))]
public class MultipleContentControl : Control
{
    ...
}

3. Update Logical Tree

At this point the control will already appear to work, but there remains some issues. If you try using ElementName bindings they won't work within TabControls, if you set a foreground on your control style you will find it doesn't inherit to the child correctly. These are all symptoms of not having set up the Logical Tree correctly.

To get things working it requires two things:

// Adjust property registration to have a property changed callback
public static readonly DependencyProperty LeftContentProperty = DependencyProperty.Register(
    nameof(LeftContent),
    typeof(object),
    typeof(MultipleContentControl),
    new FrameworkPropertyMetadata(null, OnLeftContentChanged)
    );

// in the callback update logical child
private static void OnLeftContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var c = (MultipleContentControl)d;

    c.RemoveLogicalChild(e.OldValue);
    c.AddLogicalChild(e.NewValue);
}

// override this property to enumerate each of your content properties
protected override IEnumerator LogicalChildren
{
    get
    {
        var leftContent = LeftContent;
        if (leftContent != null)
        {
            yield return leftContent;
        }
    }
}

4. Make use of the control

Of course you need to implement the ControlTemplate structure and any additional properties that make your control unique and fit for purpose, then it's ready to be used:

<controls:MultipleContentControl>
    <controls:MultipleContentControl.RightContent>
        <TextBox Text="Hello There"/>
    </controls:MultipleContentControl.RightContent>
    <CheckBox>Hello There</CheckBox>
</controls:MultipleContentControl>

#csharp #dotnet #programming #wpf