How to Create a Wpf Usercontrol with Named Content

How to create a WPF UserControl with NAMED content

It seems this is not possible when XAML is used. Custom controls seem to be a overkill when I actually have all the controls I need, but just need to group them together with a small bit of logic and allow named content.

The solution on JD's blog as mackenir suggests, seems to have the best compromise. A way to extend JD's solution to allow controls to still be defined in XAML could be as follows:

    protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);

var grid = new Grid();
var content = new ContentPresenter
{
Content = Content
};

var userControl = new UserControlDefinedInXAML();
userControl.aStackPanel.Children.Add(content);

grid.Children.Add(userControl);
Content = grid;
}

In my example above I have created a user control called UserControlDefinedInXAML which is define like any normal user controls using XAML. In my UserControlDefinedInXAML I have a StackPanel called aStackPanel within which I want my named content to appear.

Creating a WPF user control with a child element

Just set the UserControl's Template property

<UserControl ...>
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<StackPanel>
<TextBlock Text="{Binding Title,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</UserControl.Template>
</UserControl>

and put the displayed element in the UserControl's Content:

<local:DisplayContainer Title="A Title">
<TextBlock Text="Hello"/>
</local:DisplayContainer>

WPF - Hosting content inside a UserControl

The following code

<local:UserControl1>
<Button>Click me</Button>
</local:UserControl1>

Means that you set UserControl1's Content property to be that button. This button simply replaces that UserControls1's markup. So all the things that you have in UserControl1.xaml are not there any more.

EDIT

If you want your UserControl to host some markup that will be set somewhere outside of it, you can add a DependencyProperty to it, for example:

    /// <summary>
/// Gets or sets additional content for the UserControl
/// </summary>
public object AdditionalContent
{
get { return (object)GetValue(AdditionalContentProperty); }
set { SetValue(AdditionalContentProperty, value); }
}
public static readonly DependencyProperty AdditionalContentProperty =
DependencyProperty.Register("AdditionalContent", typeof(object), typeof(UserControl1),
new PropertyMetadata(null));

And add some element to it's markup to host that additional content. Here's an example extending the markup you provided:

<UserControl ... Name="userControl">
<Grid Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<TextBlock Text="Title" FontSize="30" Margin="10,0,0,0"/>
<ContentPresenter Content="{Binding AdditionalContent, ElementName=userControl}" />
</Grid>
</UserControl>

Now you can use it as following:

<local:UserControl1>
<local:UserControl1.AdditionalContent>
<Button>Click me</Button>
</local:UserControl1.AdditionalContent>
</local:UserControl1>

Include a UserControl with Content

There is nothing wrong with the first way. It simply creates a UserControl1 and sets the content to a TextBlock, hereby overriding the content you set in the definition. The second way creates a UserControl1 and leaves the content as is.

How to bind UserControl to content of custom control

Here's how I would do this. It relies on the SecondaryContent being either UI stuff (like the second example in "Usage" below), or else a viewmodel with an implicit DataTemplate. I could easily add a SecondaryDataTemplateSelector property to give the consumer more explicit control over how that templating happens.

ChartBar.cs

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

// Rather than ExpanderContent, we're inheriting ContentControl.Content for the
// main control content.

#region SecondaryContent Property
public Object SecondaryContent
{
get { return (Object)GetValue(SecondaryContentProperty); }
set { SetValue(SecondaryContentProperty, value); }
}

public static readonly DependencyProperty SecondaryContentProperty =
DependencyProperty.Register("SecondaryContent", typeof(Object), typeof(ChartBar),
new PropertyMetadata(null));
#endregion SecondaryContent Property

#region IsExpanded Property
// This is optional. I just know I'd end up wanting it.
public bool IsExpanded
{
get { return (bool)GetValue(IsExpandedProperty); }
set { SetValue(IsExpandedProperty, value); }
}

public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ChartBar),
new PropertyMetadata(false));
#endregion IsExpanded Property
}

Themes/Generic.xaml, or else App.xaml, within <Application.Resources>, or some other .xaml resource dictionary included in one or the other.

<ControlTemplate x:Key="ChartBarDefaultTemplate" TargetType="local:ChartBar">
<!--
Use Binding/RelativeSource TemplatedParent on IsExpanded so it updates both ways,
or remove that attribute/binding if you're not bothering with the IsExpanded DP.
-->
<Expander
x:Name="expander"
Header=""
VerticalAlignment="Top"
Style="{DynamicResource ExpanderStyle1}"
IsExpanded="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
>
<StackPanel Orientation="Vertical">
<ContentPresenter />
<ContentControl
Content="{TemplateBinding SecondaryContent}"
/>
</StackPanel>
</Expander>
</ControlTemplate>

<Style TargetType="local:ChartBar">
<Setter
Property="Template"
Value="{StaticResource ChartBarDefaultTemplate}"
/>
</Style>

Usage:

<StackPanel Orientation="Vertical" >
<!-- Control content -->
<local:ChartBar SecondaryContent="Secondary Content One">
<StackPanel Orientation="Vertical">
<Label>Chart Bar</Label>
<Ellipse Height="30" Width="60" Fill="GreenYellow" Opacity="0.2" />
<Label>Other stuff, etc. etc.</Label>
</StackPanel>
</local:ChartBar>

<!-- Templated viewmodel content -->
<local:ChartBar Content="{Binding RandomViewModelProperty}" IsExpanded="True">
<local:ChartBar.ContentTemplate>
<DataTemplate>
<Label Background="Beige" Content="{Binding}" Margin="20" />
</DataTemplate>
</local:ChartBar.ContentTemplate>
<local:ChartBar.SecondaryContent>
<ComboBox>
<TextBlock Text="One" />
<TextBlock Text="Two" />
<TextBlock Text="Three" />
</ComboBox>
</local:ChartBar.SecondaryContent>
</local:ChartBar>
</StackPanel>

How to create WPF usercontrol which contains placeholders for later usage

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.

WPF style overwritten when content applied to UserControl

Rename the property to something else than Content:

public partial class LinkButton : UserControl
{
public static DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(object), typeof(LinkButton));

public object ButtonContent
{
get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}

private void LinkButton_Click(object sender, RoutedEventArgs e)
{
...
}
public LinkButton()
{
InitializeComponent();
}
}

...and bind to it like this:

<Button Name ="btnLinkAddress" 
Style="{DynamicResource LinkButtonStyle}"
Content="{Binding Path=ButtonContent, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Width="Auto"
Height="Auto"
UseLayoutRounding="True"
Click="LinkButton_Click" >

If you want to set the AncestorType to LinkButton, you should define a namespace mapping:

<Button xmlns:local="clr-namespace:DDC.Controls"
Name ="btnLinkAddress"
Style="{DynamicResource LinkButtonStyle}"
Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type local:LinkButton}}}"
Width="Auto"
Height="Auto"
UseLayoutRounding="True"
Click="LinkButton_Click" >


Related Topics



Leave a reply



Submit