Search code examples
wpfuser-controlscontainersplaceholder

How to create WPF usercontrol which contains placeholders for later usage


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).


Solution

  • 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.