As a WPF control that acts as a container, how do I host the content?

I am writing a WPF control that needs to be container in the same way Border

and ScrollViewer

are containers. It's called EllipsisButtonControl

and it should place the ellipsis button to the right of its content. Here's an example of how I intend to use it:

<local:EllipsisButtonControl>
    <TextBlock Text="Testing" />
</local:EllipsisButtonControl>

      

Here's the XAML for EllipsisButtonControl

:

<ContentControl
    x:Class="WpfApplication1.EllipsisButtonControl"
    x:Name="ContentControl"
    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="30" d:DesignWidth="300">

    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />

        <Button Grid.Column="1" Command="{Binding  ElementName=ContentControl, Path=Command}" Margin="3,0" Width="30" Height="24" MaxHeight="24" VerticalAlignment="Stretch" Content="..." />

    </Grid>

</ContentControl>

      

And here's the code behind:

using System.Windows;
using System.Windows.Input;

namespace WpfApplication1
{
    public partial class EllipsisButtonControl
    {
        public EllipsisButtonControl()
        {
            InitializeComponent();
        }

        public static string GetCommand(DependencyObject obj)
        {
            return (string)obj.GetValue(CommandProperty);
        }

        public static void SetCommand(DependencyObject obj, string value)
        {
            obj.SetValue(CommandProperty, value);
        }

        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
                name: "Command",
                propertyType: typeof(ICommand),
                ownerType: typeof(EllipsisButtonControl),
                defaultMetadata: new UIPropertyMetadata());
    }
}

      

This does not work. It breaks down the constructor with System.Runtime.Remoting.RemotingException

.

I believe the binding to ContentPresenter

in EllipsisButtonControl

XAML is wrong, but I don't know how to do it correctly. What is the appropriate syntax to make this link a link to control content? (for example, TextBlock

defined in the use case)

Edit:

poke has provided a comprehensive answer below (including working code), but in the interest of others who may share their initial misunderstanding, let me briefly summarize the key concept here: container control cannot "post content" by itself. It achieves the desired effect by defining a template that changes how the caller XAML presents the content. The rest of the solution follows from this premise.

+3


source to share


3 answers


<local:EllipsisButtonControl>
    <TextBlock Text="Testing" />
</local:EllipsisButtonControl>

      

This sets up Content

your custom control. But the same as the user in the XAML control:

<ContentControl …>
    <Grid>
    </Grid>
</ContentControl>

      

The caller XAML is preemptive, so anything you do inside that user controls the XAML is effectively ignored.

The solution here is to install the custom control template. A template, in this case a control template, determines how the control itself is displayed. The simplest template for a custom control (as well as the default one) is easy to use ContentPresenter

, but of course you want to add something around that, so we need to overwrite the template. It usually looks like this:

<ContentControl …>
    <!-- We are setting the `Template` property -->
    <ContentControl.Template>
        <!-- The template value is of type `ControlTemplate` and we should
             also set the target type properly so binding paths can be resolved -->
        <ControlTemplate>

            <!-- This is where your control code actually goes -->

        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>

      

This is now the frame required to do this job. However, once you enter the control template, you need to use the appropriate binding type. Since we are writing a template and want to bind to the properties of the parent control, we need to specify the parent control as a relative origin in the bindings. But the easiest way to do this is to just use an extension TemplateBinding

. Using this ContentPresenter

can be placed in this way inside the ControlTemplate

above:

<ContentPresenter Content="{TemplateBinding Content}" />

      



And that should be all you need here to get the content handler to work.

However, now that you are using a control template, of course you need to configure other bindings as well. Specifically binding to your custom dependency property Command

. Usually this will look the same as binding the template to Content

, but since our control template targets the type ContentControl

and ContentControl

does not have your custom property, we need to explicitly reference your custom dependency property here:

<Button Command="{TemplateBinding local:EllipsisButtonControl.Command}" … />

      

Once we get this, all bindings should work fine. (In case you're wondering now: Yes, binding always targets the static dependency property for the type)


So, to summarize, your custom content control should look something like this:

<ContentControl
        x:Class="WpfApplication1.EllipsisButtonControl"
        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:local="clr-namespace:WpfApplication1"
        d:DesignHeight="30" d:DesignWidth="300" mc:Ignorable="d">
    <ContentControl.Template>
        <ControlTemplate TargetType="ContentControl">

            <Grid>
                <ContentPresenter Grid.Column="0"
                        Content="{TemplateBinding Content}" />

                <Button Grid.Column="1" Content="…"
                        Command="{TemplateBinding local:EllipsisButtonControl.Command}" />
            </Grid>

        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>

      

+1


source


Try replacing this line:

<ContentPresenter Grid.Column="0" Content="{Binding ElementName=ContentControl, Path=Content}" />

      

With the help of this



<ContentPresenter Grid.Column="0" Content={Binding Content} />

      

In your existing code, you create this ContentPresenter

displayable content EllipsesButtonControl

which includes ContentPresenter

, which should display the generated content ElipsesButtonControl

, which includes ContentPresenter

..... Unlimited recursion.

+1


source


The XAML of your EllipsisButtonControl already sets its content to the top level grid. You probably need to create ControlTemplate

eg. eg:

<ContentControl x:Class="WpfApplication1.EllipsisButtonControl"
                x:Name="ContentControl"
                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="30" d:DesignWidth="300">
    <ContentControl.Template>
        <ControlTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>

                <ContentPresenter Grid.Column="0"
                    Content="{Binding ElementName=ContentControl, Path=Content}"/>

                <Button Grid.Column="1"
                    Command="{Binding ElementName=ContentControl, Path=Command}"
                    Margin="3,0" Width="30" Height="24" MaxHeight="24"
                    VerticalAlignment="Stretch" Content="..." />
            </Grid>
        </ControlTemplate>
    </ContentControl.Template>
</ContentControl>

      

+1


source







All Articles