views:

3360

answers:

4

I have a set of controls with attached commands and logic that are constantly reused in the same way. I decided to create a user control that holds all the common controls and logic.

However I also need the control to be able to hold content that can be named. I tried the following:

<UserControl.ContentTemplate>
    <DataTemplate>
        <Button>a reused button</Button>
        <ContentPresenter Content="{TemplateBinding Content}"/>
        <Button>a reused button</Button>
    </DataTemplate>
</UserControl.ContentTemplate>

However it seems any content placed inside the user control cannot be named. For example if I use the control in the following way:

<lib:UserControl1>
     <Button Name="buttonName">content</Button>
</lib:UserControl1>

I receive the following error:

Cannot set Name attribute value 'buttonName' on element 'Button'. 'Button' is under the scope of element 'UserControl1', which already had a name registered when it was defined in another scope.

If I remove the buttonName, then it compiles, however I need to be able to name the content. How can I achieve this?

+4  A: 

This guy found a solution involving getting rid of his custom control's XAML file, and building the custom control's UI programmatically.

This blog post has more to say on the subject.

mackenir
Thanks mackenir! I think JD's blog is the best option I from ones I have found
Ryan
A: 

Check out Programming WPF by Chris Sells & Ian Griffiths. Chapter 9: Control Templates covers sing Named parts in a control template.

Paul Alexander
Thanks Paul, I haven't got this book, but I'm guessing that the section is talking about named controls WITHIN the template, rather than controls placed inside the control content when the control is actually in use?
Ryan
+5  A: 

It seems this is not possible when XAML is used. Custom controls seem to be a overkill when I actually have all the controls I need, but just need to group them together with a small bit of logic and allow named content.

The solution on JD's blog as mackenir suggests, seems to have the best compromise. A way to extend JD's solution to allow controls to still be defined in XAML could be as follows:

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        var grid = new Grid();
        var content = new ContentPresenter
                          {
                              Content = Content
                          };

        var userControl = new UserControlDefinedInXAML();
        userControl.aStackPanel.Children.Add(content);

        grid.Children.Add(userControl);
        Content = grid;           
    }

In my example above I have created a user control called UserControlDefinedInXAML which is define like any normal user controls using XAML. In my UserControlDefinedInXAML I have a StackPanel called aStackPanel within which I want my named content to appear.

Ryan
I've been finding that I get data binding problems when using this mechanism of re-parenting the content. The data-binding seems set up correctly, but the initial populating of the controls from the data source doesn't work properly. I think the problem is limited to controls that are not the direct child of the content presenter.
mackenir
Not had a chance to experiment with this since, but I've not had any problems databinding to either the controls defined in the UserControlDefinedInXAML (from above example) or the controls added to the ContentPresenter so far. I have been databinding through code only though (not XAML - not sure if that makes a difference).
Ryan
It seems it DOES make a difference. I just tried using XAML for my data bindings in the case you described and it does not work. But if I set it in code, it does work!
Ryan
+2  A: 

The answer is to not use a UserControl to do it.

Create a class that extends ContentControl

public class MyFunkyControl : ContentControl
{
    public static readonly DependencyProperty HeadingProperty =
        DependencyProperty.Register("Heading", typeof(string),
        typeof(HeadingContainer), new PropertyMetadata(HeadingChanged));

    private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((HeadingContainer) d).Heading = e.NewValue as string;
    }

    public string Heading { get; set; }
}

then use a style to specify the contents

<Style TargetType="control:MyFunkyControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="control:MyFunkyContainer">
                <Grid>
                    <ContentControl Content="{TemplateBinding Content}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

and finally - use it

<control:MyFunkyControl Heading="Some heading!">            
    <Label Name="WithAName">Some cool content</Label>
</control:MyFunkyControl>
Gumpifoo
I found this to actually the most comfortable solution since you can flesh out the ControlTemplate in an ordinary UserControl using the designer and that transform it into a style with associated control template.
Oliver Weichhold