6

I want to create a user control that contains a TextBlock and a StackPanel that will allow the user to add his/her own controls to the user control dynamically in XAML.

Here is the sample XAML for my UserControl:

<UserControl x:Class="A1UserControlLibrary.UserControlStackPanel"
             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="200" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="I want the user to be able to add any number of controls to the  StackPanel below this TextBlock."
                   FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
        <StackPanel>
            <!-- I want the user to be able to add any number of controls here -->
        </StackPanel>
    </StackPanel>
</UserControl>

I would like the user to be able to embed this user control in their XAML and add their own controls to the stack panel of the user control:

<uc:A1UserControl_StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
    <Button Name="MyButton1" Content="Click" Height="30" Width="50"/>
    <Button Name="MyButton2" Content="Click" Height="30" Width="50"/>
    <Button Name="MyButton3" Content="Click" Height="30" Width="50"/>
</uc:A1UserControl_StackPanel>

Doing this using the above XAML does not work. Any ideas?

rae1
  • 6,066
  • 4
  • 27
  • 48
PeterBuilt
  • 99
  • 1
  • 6

1 Answers1

7

You can do that, although not quite like your example. You need two things. The first is to declare a DependencyProperty of type UIElement, of which all controls extend:

public static DependencyProperty InnerContentProperty = DependencyProperty.Register("InnerContent", typeof(UIElement), typeof(YourControl));

public UIElement InnerContent
{
    get { return (UIElement)GetValue(InnerContentProperty); }
    set { SetValue(InnerContentProperty, value); }
}

The second is to declare a ContentControl in the XAML where you want the content to appear:

<StackPanel>
    <TextBlock Text="I want the user to be able to add any number of controls to the  StackPanel below this TextBlock."
               FontFamily="Arial" FontSize="12" FontWeight="DemiBold" Margin="5,10,5,10" TextWrapping="Wrap"/>
    <StackPanel>
        <ContentControl Content="{Binding InnerContent, RelativeSource={RelativeSource AncestorType={x:Type YourXmlNamspacePrefix:ContentView}}}" />
    </StackPanel>
</StackPanel>

In my opinion, if you use StackPanels, you could find that your content does not get displayed correctly... I'd advise you to use Grids for layout purposes for all but the simplest layout tasks.

Now the one difference to your example is in how you would use your control. The InnerContent property is of type UIElement, which means that it can hold one UIElement. This means that you need to use a container element to display more than one item, but it has the same end result:

<YourXmlNamspacePrefix:YourControl>
    <YourXmlNamspacePrefix:YourControl.InnerContent>
        <StackPanel x:Name="MyUserControl_Test" Margin="10" Height="100">
            <Button Content="Click" Height="30" Width="50"/>
            <Button Content="Click" Height="30" Width="50"/>
            <Button Content="Click" Height="30" Width="50"/>
        </StackPanel>
    </YourXmlNamspacePrefix:YourControl.InnerContent>
</YourXmlNamspacePrefix:YourControl>

And the result:

enter image description here


UPDATE >>>

For the record, I know exactly what you want to do. You, it seems, do not understand what I am saying, so I'll try to explain it one last time for you. Add a Button with the Tag property set as I've already shown you:

<Button Tag="MyButton1" Content="Click" Click="ButtonClick" />

Now add a Click handler:

private void ButtonClick(object sender, RoutedEventArgs e)
{
    Button button = (Button)sender;
    if (button.Tag = "MyButton1") DoSomething();
}

That's all there is to it.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thank you! I was thinking that some sort of content property would be needed but I wasn't sure how to go about it. – PeterBuilt Jan 29 '14 at 23:22
  • Your solution works well. One question, though. If I attempt to give a name to one of the controls I have embedded in my user control (such as naming one of the buttons MyButton1) I get an error: "Error 1 Cannot set Name attribute value 'MyButton1' on element 'Button'. 'Button' is under the scope of element 'A1UserControl_Dynamic', which already had a name registered when it was defined in another scope." – PeterBuilt Jan 30 '14 at 00:52
  • It seems as though there's a [bit of a bug there](https://connect.microsoft.com/VisualStudio/feedback/details/483024/wpf-error-message-cannot-set-name-attribute-value-0-on-element-1-1-is-under-the-scope-of-element-2-which-already-had-a-name-registered-when-it-was-defined-in-another-scope)... the simplest solution is just to use the `Tag` property to identify them: `` – Sheridan Jan 30 '14 at 09:02
  • I can set a Tag on each Button but I do not understand how I can use the tag to reference the Button in code behind in order to change one of its properties. – PeterBuilt Jan 30 '14 at 15:32
  • Really? How about `if (button.Tag == "Some name") DoSomething();`? – Sheridan Jan 30 '14 at 15:35
  • If I can't give a name to the button I can't use 'if (button.Tag.ToString() == "Some name") DoSomething();' I guess I'm missing something obvious but don't know what it is. – PeterBuilt Jan 30 '14 at 15:57
  • What???? Use the `Tag` property AS IF IT WERE the `Name` property: `button.Tag = "Some name"` or ``. – Sheridan Jan 30 '14 at 16:12
  • OK, I guess you mean that in code behind I need to enumerate all the child controls and for each child Button found check its Tag value and then perform my desired action on that Button. Or is there an easier way? – PeterBuilt Jan 30 '14 at 16:20
  • Gosh, no that's *not* what I mean. I just showed you how to set a name into the `Tag` property in two ways. *Your* original code was like this: ``. I'm saying that you can do this: ``. Please continue your discoveries *on your own*. – Sheridan Jan 30 '14 at 16:26
  • I think you misunderstand what I want to do. I want to set a name on the button was so that I can reference it in code behind. Since I cannot set the Name property I set the Tag property. However, I cannot reference the button in code behind using Tag. For example, with the Name property on the button I could do the following: MyButton.Content = "Click Me"; However, I don't know how to do this using Tag. This is a simple example but I need to extend it to more complex situations. I will research this further on my own if you do not have a simple solution. Thanks you again. – PeterBuilt Jan 30 '14 at 16:48
  • I'm trying this in a UWP project, and I'm getting "The member 'AncestorType' is not recognized or is not accessible." from the ContentControl XAML. I also had to add a null fourth argument to Register() that's required. Any ideas? Because I'm drawing a big blank on most of this from the docs. – Thought Apr 09 '17 at 03:49