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
.
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.
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.
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.
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>
You cannot bind dependency properties of type UIElementCollection
, generally. Try something like this:
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>
Important to note:
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.UserControl
. This does not necessitate a completely custom control.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.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;
}
}
}
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>
Just remove the UserControl tag and replace with Grid
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:
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" >
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With