36

I'd better ask the question by example. Let's say I have UserControl and Window which uses this control.

I would like to design this control (named MyControl) in such way (this is sci-fi syntax!):

<Grid>
  <Button>Just a button</Button>
  <PlaceHolder Name="place_holder/>
</Grid> 

and use in such ways when designing my Window:

<MyControl/>

or

<MyControl>
  <place_holder>
    <Button>Button 1</Button>
  </place_holder>
</MyControl> 

or

<MyControl>
  <place_holder>
    <Button>Button 1</Button>
    <Button>Button 2</Button>
  </place_holder>
</MyControl> 

Of course I would like to have ability to add even more elements to MyControl in Window. So, in a way it should work as container (like Grid, StackPanel, and so on). The placement would be defined in UserControl (in this example after button "Just a button") but what to add (what elements) would be defined in Window (where UserControl -- MyControl -- is used).

I hope this is clear what I would like to achieve. The key point is using XAML when designing Window, so my class should be no worse than other controls.

Now, the big QUESTION is -- how to do it?

Remarks: styling is out of scope. All I want to do is add any controls I want to MyControl when designing Window (not when designing MyControl).

greenoldman
  • 16,895
  • 26
  • 119
  • 185

2 Answers2

50

ContentControls & ItemsControls are good for this, you can bind them to a property of your UserControl or expose them.

Using a ContentControl (for placeholders in multiple disconnected places):

<UserControl x:Class="Test.UserControls.MyUserControl2"
             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" 
             Name="control">
    <Grid>
        <Button>Just a button</Button>
        <ContentControl Content="{Binding PlaceHolder1, ElementName=control}"/>
    </Grid>
</UserControl>
public partial class MyUserControl2 : UserControl
{
    public static readonly DependencyProperty PlaceHolder1Property =
        DependencyProperty.Register("PlaceHolder1", typeof(object), typeof(MyUserControl2), new UIPropertyMetadata(null));
    public object PlaceHolder1
    {
        get { return (object)GetValue(PlaceHolder1Property); }
        set { SetValue(PlaceHolder1Property, value); }
    }

    public MyUserControl2()
    {
        InitializeComponent();
    }
}
<uc:MyUserControl2>
    <uc:MyUserControl2.PlaceHolder1>
        <TextBlock Text="Test"/>
    </uc:MyUserControl2.PlaceHolder1>
</uc:MyUserControl2>

ItemsControl-Version (for collections in one place)

<UserControl x:Class="Test.UserControls.MyUserControl2"
             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"
             Name="control">
    <Grid>
        <Button>Just a button</Button>
        <ItemsControl Name="_itemsControl" ItemsSource="{Binding ItemsSource, ElementName=control}"/>
    </Grid>
</UserControl>
[ContentProperty("Items")]
public partial class MyUserControl2 : UserControl
{
    public static readonly DependencyProperty ItemsSourceProperty = 
        ItemsControl.ItemsSourceProperty.AddOwner(typeof(MyUserControl2));
    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public ItemCollection Items
    {
        get { return _itemsControl.Items; }
    }

    public MyUserControl2()
    {
        InitializeComponent();
    }
}
<uc:MyUserControl2>
    <TextBlock Text="Test"/>
    <TextBlock Text="Test"/>
</uc:MyUserControl2>

With UserControls you can decide to expose certain properties of internal controls; besides the ItemsSource one probably would want to also expose properties like the ItemsControl.ItemTemplate, but it all depends on how you want to use it, if you just set the Items then you do not necessarily need any of that.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 3
    `DataContext="{Binding RelativeSource={RelativeSource Self}}` is **bad**, because it will prevent binding to actual data. The UserControl won't be able to inherit the DataContext from its parent, and if you define it explicitly when using the control, the binding on PlaceHolder1 won't work. You need to use a RelativeSource or ElementName binding instead. – Thomas Levesque Apr 22 '11 at 18:22
  • Thank you very much. After the fix with DataContext it works like a charm. – greenoldman Apr 22 '11 at 18:41
  • I just tend to set the DataContext like that because in Windows it does not matter because they have no parent anyway. – H.B. Apr 22 '11 at 18:44
  • 1
    @H.B., me too (in Windows), but here indeed it is better not too. Btw. odd outcome, with this approach you cannot name nested controls (TextBlock in your examples) without special hoops with code. See: http://stackoverflow.com/questions/751325/how-to-create-a-wpf-usercontrol-with-named-content Of course no complains, just saying. Maybe someone will benefit from all the wisdom gathered here in future :-) – greenoldman Apr 22 '11 at 19:07
  • Oh yeah, i am aware of that, that might be a bit problematic indeed. – H.B. Apr 22 '11 at 19:09
23

I think you want to set your UserControl's ControlTemplate with a ContentPresenter located inside (so you can define where the Content will be presented).

Your Custom UserControl:

<UserControl x:Class="TestApp11.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <UserControl.Template>
        <ControlTemplate>
            <StackPanel>
                <TextBlock Text="Custom Control Text Area 1" />
                <ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
                <TextBlock Text="Custom Control Text Area 2" />
            </StackPanel>
        </ControlTemplate>
    </UserControl.Template>
</UserControl>

Usage:

<Window x:Class="TestApp11.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:l="clr-namespace:TestApp11"
    Title="Window1" Height="250" Width="200">
    <StackPanel>
        <l:UserControl1>
            <Button Content="My Control's Content" />
        </l:UserControl1>
    </StackPanel>
</Window>

enter image description here

If you need multiple items in your content section, simply place them in a container like a grid or a stackpanel:

<l:UserControl1>
    <StackPanel>
        <Button Content="Button 1" />
        <Button Content="Button 2" />
    </StackPanel>
</l:UserControl1>
Scott
  • 11,840
  • 6
  • 47
  • 54
  • Hmm, thank you for your help and link, but the article is doing something that I already know -- it defines some user controls, and then uses them. That's it -- the user controls does not act as container, please note that their content are defined at the control level, not window -- and this is the point of my question. – greenoldman Apr 22 '11 at 18:11
  • @macias, the link only contained of few of the concepts I was getting at... but did not put it together the way I was thinking. I've attached a sample of what I meant. – Scott Apr 22 '11 at 18:31
  • thank you for clarification, I am not that familiar with WPF to see a container through styling "fog" ;-) I am marking H.B. reply as answer because despite your solution is code-free, I have already designed user-control, and H.B. approach required less changes to the existing code. Nevertheless big thank you! – greenoldman Apr 22 '11 at 18:44
  • @macias... no worries... as long as you found a solution that fits your needs, that's all that matters! – Scott Apr 22 '11 at 18:46
  • 3
    There is a problem, what ever the content you place inside the user control e.g. x can't have named content. How to add a content with named elements? The compilation error is Cannot set Name attribute value x on element 'x. x is under the scope of element y, which already had a name registered when it was defined in another scope. – Sriwantha Attanayake Sep 10 '12 at 08:38