Adding children to UserControl

33,550

Solution 1

You cannot bind dependency properties of type UIElementCollection, generally. Try something like this:

MultiChildDemo.xaml

Nothing much to see here. The StackPanel will hold our child elements. You could obviously do quite a bit more.

Code:

<UserControl x:Class="Demo.MultiChildDemo"
             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:demo="clr-namespace:Demo"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel x:Name="PART_Host" />
</UserControl>

MultiChildDemo.xaml.cs

Important to note:

  • The ContentPropertyAttribute attribute sets the property that will be set by any elements enclosed by the parent element of this type. Thus, any elements within <MultiChildDemo></MultiChildDemo> will be added to the Children property.
  • We are extending a UserControl. This does not necessitate a completely custom control.
  • It is good practice in WPF to make properties using DependencyProperty.Register() and variants. You will notice that there is no backing variable for the Children property: DependencyProperty takes care of storing the data for us. Were we not creating a read-only property, this would enable the use of bindings and other cool WPF features. Thus, it is important to get into the habit of using dependency properties, rather than plain properties as you often see in examples around the Internet.
  • This is a relatively simple dependency property example. All we do is copy the reference to a child's dependency property, thereby forwarding calls to UIElementCollection.Add. Much more complex examples are out there, especially on MSDN.

Code:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace Demo
{
    [ContentProperty(nameof(Children))]  // Prior to C# 6.0, replace nameof(Children) with "Children"
    public partial class MultiChildDemo : UserControl
    {
        public static readonly DependencyPropertyKey ChildrenProperty = DependencyProperty.RegisterReadOnly(
            nameof(Children),  // Prior to C# 6.0, replace nameof(Children) with "Children"
            typeof(UIElementCollection),
            typeof(MultiChildDemo),
            new PropertyMetadata());

        public UIElementCollection Children
        {
            get { return (UIElementCollection)GetValue(ChildrenProperty.DependencyProperty); }
            private set { SetValue(ChildrenProperty, value); }
        }

        public MultiChildDemo()
        {
            InitializeComponent();
            Children = PART_Host.Children;
        }
    }
}

MultiChildDemoWindow.xaml

Note how the labels are direct descendants of the <demo:MultiChildDemo> element. You could also enclose them in a <demo:MultiChildDemo.Children> element. The ContentPropertyAttribute attribute that we added to the MultiChild class allows us to omit this step, though.

Code:

<Window x:Class="Demo.MultiChildDemoWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:demo="clr-namespace:Demo"
        Title="MultiChildDemoWindow" Height="300" Width="300">
    <demo:MultiChildDemo>
        <Label>Test 1</Label>
        <Label>Test 2</Label>
        <Label>Test 3</Label>
    </demo:MultiChildDemo>
</Window>

Solution 2

Just remove the UserControl tag and replace with Grid

Solution 3

First of all try to understand the difference between an User Control and a Custom Control (Control/Content Control)

To keep it simple:

"The standard WPF controls provide a great deal of built-in functionality. If the functionality of one of the preset controls, such as a progress bar or a slider, matches the functionality that you want to incorporate, then you should create a new template for that preexisting control to achieve the appearance you want. Creating a new template is the simplest solution to creating a custom element, so you should consider that option first.

If the functionality you want to incorporate into your application can be achieved through a combination of preexisting controls and code, consider creating a user control. User controls enable you to bind together multiple preexisting controls in a single interface and add code that determines how they behave.

If no preexisting control or combination of controls can achieve the functionality you want, create a custom control. Custom controls enable you to create a completely new template that defines the visual representation of the control and to add custom code that determines the control’s functionality."

Adam Nathan - WPF Unleashed 4

Now if all you want is a ContentControl:

  1. Make a new CustomControl that derives ContentControl
  2. Locate the generic.xaml in themes and add a Content Presenter to your control template. As said above, the custom control logic is separated from it's visual presentation
  3. Use the control as a regular ContentControl.

For multiple items as Content take a look at ItemsControl

The steps above are modified as:

Derive Items Control

public class MyCtrl : ItemsControl
{
    static MyCtrl()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCtrl), new FrameworkPropertyMetadata(typeof(MyCtrl)));
    }
}

Modify Generic.xaml to include ItemsPresenter

<Style TargetType="{x:Type local:MyCtrl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCtrl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <ItemsPresenter />
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Use the control

<StackPanel>
    <ctrl:MyCtrl>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
        <Button Width="100" Height="50">Click</Button>
    </ctrl:MyCtrl>
</StackPanel>

As said above, for this simple case it would not be necessary to derive ItemsControl but to simply use the ItemsControl and define a Template for it. Derive ItemsControl when planning to extend by by adding functionality

EDIT:

The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin

You should set the Border properties on your control itself:

<ctrl:MyCtrl BorderBrush="Red" BorderThickness="3" Background="Green" >
Share:
33,550

Related videos on Youtube

Felix K.
Author by

Felix K.

Working as a freelancer in/around Munich. E-Mail: [email protected] Dependency-Property-System: http://github.com/FelixKlakow/Jupiter.Core

Updated on July 24, 2022

Comments

  • Felix K.
    Felix K. almost 2 years

    My task

    Create a UserControl which should be able to contain any visual child which is available in WPF, the children are displayed in a container which is a child of the UserControl.

    My Problem

    I can't manage to get the children displayed correctly in my container, i tried serval ways and did not find a way which works in the designer. I also tried to use ContentControl but nothing gets displayed.

    My approaches

    First i found this link and i tried it with some variations. I managed to display the content in the right container but it does not work in the designer because the content-property is set-private and the designer want to override it. Placing everything in XAML works but this is not good when working with designers. This is may favorite way.

    After this i tried to use ContentControl by binding it's Content-property to a bindable property of the UIElementCollection-type. This aproach is not throwing any errors in the designer, but i have to admit that i never see any control ( e.g. a Button ) in my container. It stays empty but has the children added.

    Conclusion

    After serval hours of searching for a easy and quick solution i decided to ask for solutions here. I'm a little disappointed. It would be really helpful if Microsoft could get a sample into MSDN.

    I'm sure there must be a easy way to archive this.

    Current situation

    Thanks to Andrei Gavrila and jberger i archived to create a node which displays the content ( see code below ) but there are still two issues: - No designer support - The border ( see xaml ) is not shown in designer and not shown when the app is running there is even no margin

    public class NodeContent : ContentControl
    {
        static NodeContent()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NodeContent), new FrameworkPropertyMetadata(typeof(NodeContent)));
        }
    }
    
    public partial class Node : UserControl, INotifyPropertyChanged
    {
        UIElementCollection _Elements;
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public UIElementCollection NodeContent
        {
            get { return _Elements; }
            set
            {
                _Elements = value;
                OnPropertyChanged("NodeContent");
            }
        }
    
        public Node()
        {
            InitializeComponent();
            NodeContent = new UIElementCollection(NodeContentContainer, this);
        }
    
    
    
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }
    

    Node-Xaml:

    <UserControl x:Class="Pipedream.Nodes.Node"
                 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="216" d:DesignWidth="174" Background="Transparent" Name="NodeControl" xmlns:my="clr-namespace:Pipedream.Nodes">
    
        <Border BorderThickness="1" CornerRadius="20" BorderBrush="Black" Background="White">
            <Grid>
                <my:NodeContent x:Name="NodeContentContainer" Margin="20" Content="{Binding Source=NodeControl, Path=NodeContent}" />
            </Grid>
        </Border>
    </UserControl>
    

    Generic-Xaml:

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Pipedream.Nodes">
    
    
        <Style TargetType="{x:Type local:NodeContent}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:Node}">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <ContentPresenter />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>
    
    • deerchao
      deerchao over 12 years
      Or, maybe, perhaps, is it possible that you can try using ControlTemplate to transform a Panel, instead of creating a new Control?
    • Felix K.
      Felix K. over 12 years
      @deerchao I really need a own UserControl. But thank you for the suggestion.
    • Jake Berger
      Jake Berger over 12 years
      under "My approaches", the 2nd paragraph seems contradicting: "It stays empty but has the children added."..?
    • Felix K.
      Felix K. over 12 years
      @jberger Oh, this simply means that no childs are visible. :-)
    • Jake Berger
      Jake Berger over 12 years
      what template/theme have you defined to display the custom ContentControl?
    • Felix K.
      Felix K. over 12 years
      @jberger I don't have the code here, but if you know a answer which works feel free to add it.
    • Jake Berger
      Jake Berger over 12 years
      @FelixK. it should be similar to Andrei's answer, replacing ItemsControl & ItemsPresenter with ContentControl & ContentPresenter.
    • Jake Berger
      Jake Berger over 12 years
      @FelixK. I think it'd really help us if we knew more exactly what you're trying to do. What do you want the final result to be? What I take out of it is that you want a control which can be used with designer support. WPF provides this. Do you want your control to "look" a certain way? "Behave" a certain way? If so, how exactly?
    • Jake Berger
      Jake Berger over 12 years
      why must it be a UserControl?
    • Felix K.
      Felix K. over 12 years
      @jberger As i already told you i'm creating a node based system ( web.student.tuwien.ac.at/~e0427417/downloads/… ). A little bit more complex than the image. So i have some basic buttons, textfields etc for each node.
    • Jake Berger
      Jake Berger over 12 years
      how does the person designing the diagram indicate whether a node property is an input or output?
    • Felix K.
      Felix K. over 12 years
      @jberger This is generated automated, but you can customize the nodes for special types.
  • Felix K.
    Felix K. over 12 years
    Did you test your code? It shows nothing to me. Anyway your MyCtrl seems just to be a container for other controls. But this is not gonna help me. I want to create a combination of preexisting controls, a UserControl, which has one child container ( could be a grid or something else ) which can contain any types of UIElements. So i'm not sure how this is gonna help me. But thanks anyway.
  • Andrei Gavrila
    Andrei Gavrila over 12 years
    Sure I did. Here's a link with sources skydrive.live.com/… .It should help. Your control can contain other controls as well. Just add them to your class, expose their inner properties on the parent control itself as DP and improve the template to display them
  • Felix K.
    Felix K. over 12 years
    Ok thank you, but i gonna wait for other answers because i want to have the possibility to design the nodes without using a template.
  • Jake Berger
    Jake Berger over 12 years
    @FelixK.: unfortunately, the way WPF works, Andrei is correct. i would be using a custom control derived from Control, ContentControl, or ItemsControl OR using Templates, DataTemplates, etc. Given that you didn't give much context as to why you need this exact solution, it's difficult to direct you to which one may actually work best.
  • Felix K.
    Felix K. over 12 years
    @jberger + Andrei Gavrila: Works now but can't use the designer to place content in the node.
  • Jake Berger
    Jake Berger over 12 years
    @FelixK. now that I see what you're trying to achieve, I'd recommend going with Andrei's solution. Create your Node which inherits ItemsControl. Define a template which has the collapser triangle, the + button, etc. Create NodeOutput and NodeInput which both inherit ContentControl (so you can stick in whatever WPF control you need). Define templates for both to show the connection points, ContentPresenter, etc. The root should be of type Canvas so you can move your Nodes around at will.
  • Zenexer
    Zenexer over 12 years
    This is really a "hackish" way to do it. You're attempting to attribute XAML with a custom control by using a style--that's not how it works. If you want to make a custom control, use C#. Not XAML. You're better off either doing it fully in C# or using a UserControl. The C# option is, obviously, quite a mess itself, but at least it's not hackish. The UserControl option is the best route, in my opinion.
  • Andrei Gavrila
    Andrei Gavrila over 12 years
    Hi Zenexer. If your comment relates to my answer, you are wrong. Custom Controls in WPF have no designable surface, and defining a template in Generic.xaml is the corect way to do it, not a hack. For more information please take a look at MCTS Self-Paced Training Kit (Exam 70-511), Chapter 5-Lesson 3-Creating Custom Controls Paragraph, and the Control Class on msdn
  • Felix K.
    Felix K. over 12 years
    @AndreiGavrila Related to your edit: My code above ( see my edit ) is the code i'm talking about.
  • Felix K.
    Felix K. over 12 years
    Great!! I replaced your StackPanel with a Grid. After placing the MultiChildDemo-item in the main window i placed a grid in it. Finally the design view works perfectly and i can use the designer. Thanks a lot.
  • Zenexer
    Zenexer over 12 years
    @AndreiGavrila I'm not a big fan of exams like that. I find them to go against good programming ethics. Also, MSDN has its fair share of errors. "You are wrong" is a bad way to respond to an opinion, anyway.
  • trampster
    trampster over 10 years
    This works, however when in the designer you get "The property 'Content' is set more then once." It still builds and runs fine.
  • Andreas
    Andreas over 10 years
    Yes I tested it with labels as children
  • Livven
    Livven over 9 years
    Amazingly, this actually works, even in WinRT. The VS/Blend designer won't display any children contained in the original Grid XAML once you add any other children to it, but otherwise this works just fine and is by far the most straightforward solution.
  • LoRdPMN
    LoRdPMN about 8 years
    Perfect answer. Only for completeness, if using C# 6.0 one should use nameof(Children), instead of "Children".
  • Zenexer
    Zenexer over 7 years
    @LoRdPMN Thanks, updated. Also corrected syntax highlighting for XAML.
  • Zenexer
    Zenexer over 7 years
    I've done this before as a quick-and-dirty alternative to user controls. It's a bit hackish, but it usually works fine if you're not doing anything too fancy. It's been quite a while, but if I recall correctly, you're basically just extending Grid instead of UserControl.
  • Walter
    Walter over 7 years
    Thanks for the awesome solution. I know I'm several years into this discussion but... If I take your solution and change 1 thing, bind the Label content it doesn't work (ie <Label Content = "{Binding myDataContextProperty}" /> ). I've tried every combination of binding properties without success. Hard code the content to "Hello", works like a charm. Any ideas?
  • Rans
    Rans over 3 years
    How can I only allow one child? i tried UIElement, but it doesn't work