I created a CustomControl that has two TemplateParts.
using System;
using System.Windows;
using System.Windows.Controls;
namespace WpfCustomControlLibrary1
{
[TemplatePart(Name = "PART_ControlsLayer", Type = typeof (ContentPresenter))]
[TemplatePart(Name = "PART_DisplayLayer", Type = typeof (ContentPresenter))]
public class CustomControl1 : Control
{
public static readonly DependencyProperty ControlsLayerProperty =
DependencyProperty.Register("ControlsLayer", typeof (object), typeof (CustomControl1),
new UIPropertyMetadata(null));
public static readonly DependencyProperty DisplayLayerProperty =
DependencyProperty.Register("DisplayLayer", typeof (object), typeof (CustomControl1),
new UIPropertyMetadata(null));
private ContentPresenter partControlsLayer;
private ContentPresenter partDisplayLayer;
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (CustomControl1),
new FrameworkPropertyMetadata(typeof (CustomControl1)));
}
public object ControlsLayer
{
get { return GetValue(ControlsLayerProperty); }
set { SetValue(ControlsLayerProperty, value); }
}
public object DisplayLayer
{
get { return GetValue(DisplayLayerProperty); }
set { SetValue(DisplayLayerProperty, value); }
}
public override void OnApplyTemplate()
{
ApplyTemplate();
partControlsLayer = GetTemplateChild("PART_ControlsLayer") as ContentPresenter;
partDisplayLayer = GetTemplateChild("PART_DisplayLayer") as ContentPresenter;
if (partControlsLayer == null || partDisplayLayer == null)
{
throw new NullReferenceException("Template parts not available");
}
}
}
}
In the Generic.xaml I defined the ControlTemplate and a Default-Setter for the DisplayLayer (one of the TemplateParts). Finaly I set this as the Template of CustomControl1.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<Style TargetType="{x:Type local:CustomControl1}">
<Style.Resources>
<ControlTemplate x:Key="DefaulTemplate" TargetType="{x:Type local:CustomControl1}">
<Grid>
<ContentPresenter x:Name="PART_ControlsLayer"
Content="{TemplateBinding ControlsLayer}" />
<ContentPresenter x:Name="PART_DisplayLayer"
Content="{TemplateBinding DisplayLayer}" />
</Grid>
</ControlTemplate>
</Style.Resources>
<Setter Property="DisplayLayer">
<Setter.Value>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding FunctionName, FallbackValue=Functionname}" />
<TextBlock Grid.Row="1" Text="{Binding DisplayValue, FallbackValue=0.0dB}" Foreground="Lime"
Background="Black" />
</Grid>
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource DefaulTemplate}"/>
</Style>
<Style TargetType="{x:Type local:CustomControl2}" BasedOn="{StaticResource {x:Type local:CustomControl1}}" />
</ResourceDictionary>
Now I create a CustomControl2 BasedOn CustomControl1.
using System.Windows;
namespace WpfCustomControlLibrary1
{
public class CustomControl2 : CustomControl1
{
static CustomControl2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl2), new FrameworkPropertyMetadata(typeof(CustomControl2)));
}
}
}
Then I put both Controls on a WpfWindow.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<wpfCustomControlLibrary1:CustomControl1 Grid.Row="0"/>
<wpfCustomControlLibrary1:CustomControl2 Grid.Row="1"/>
</Grid>
</Window>
The Problem is I see the Defaulttemplate only on the second Control. I can't find a solution for this, please help.
#gomi42 - if I change Generic.xaml to this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomControlLibrary1">
<Style x:Key="BaseStyle" TargetType="{x:Type local:CustomControl1}">
<Style.Resources>
<ControlTemplate x:Key="DefaulTemplate" TargetType="{x:Type local:CustomControl1}">
<Grid>
<ContentPresenter x:Name="PART_ControlsLayer"
Content="{TemplateBinding ControlsLayer}" />
<ContentPresenter x:Name="PART_DisplayLayer"
Content="{TemplateBinding DisplayLayer}" />
</Grid>
</ControlTemplate>
</Style.Resources>
<Setter Property="Template" Value="{StaticResource DefaulTemplate}"/>
<Setter Property="DisplayLayer">
<Setter.Value>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding FunctionName, FallbackValue=Functionname}" />
<TextBlock Grid.Row="1" Text="{Binding DisplayValue, FallbackValue=0.0dB}" Foreground="Lime"
Background="Black" />
</Grid>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CustomControl1}" BasedOn="{StaticResource BaseStyle}" />
<Style TargetType="{x:Type local:CustomControl2}" BasedOn="{StaticResource BaseStyle}" />
</ResourceDictionary>
nothing changes! It's still visible only in the second control.
The style you created only applies to CustomControl1 because of this:
<Style TargetType="{x:Type local:CustomControl1}">
...
You need to create a new style for CustomControl2 e.g. by inheriting:
<Style x:Key="MyBase" TargetType="{x:Type local:CustomControl1}">
...
</Style>
<Style TargetType="{x:Type local:CustomControl1}" BasedOn={StaticResource MyBase} />
<Style TargetType="{x:Type local:CustomControl2}" BasedOn={StaticResource MyBase} />
I found it!
It's the DependencyProperty which causes the problem. I implemented the CustomControl like the examples on this site:
http://www.kunal-chowdhury.com/2011/04/how-to-implement-template-binding-in.html
and what should I say this example is wrong!
Here is how it works.
First CustomControl.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using WpfCustomControlLibrary1.Annotations;
namespace WpfCustomControlLibrary1
{
[TemplatePart(Name = "PART_ControlsLayer", Type = typeof (Grid))]
[TemplatePart(Name = "PART_DisplayLayer", Type = typeof(ContentControl))]
public class CustomControl1 : Control
{
private Grid partControlsLayer;
private ContentControl partDisplayLayer;
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (CustomControl1),
new FrameworkPropertyMetadata(typeof (CustomControl1)));
}
public CustomControl1()
{
DataContext = this;
}
public override void OnApplyTemplate()
{
ApplyTemplate();
partControlsLayer = GetTemplateChild("PART_ControlsLayer") as Grid;
partDisplayLayer = GetTemplateChild("PART_DisplayLayer") as ContentControl;
if (partControlsLayer == null || partDisplayLayer == null)
{
//throw new NullReferenceException("Template parts not available");
}
}
}
}
Then 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:WpfCustomControlLibrary1">
<Style x:Key="DefaultTemplate" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding FunctionName, FallbackValue=Functionname}" />
<TextBlock Grid.Row="1" Text="{Binding DisplayValue, FallbackValue=0.0dB}" Foreground="Lime"
Background="Black" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl x:Name="PART_DisplayLayer" Style="{StaticResource DefaultTemplate}" />
<Grid x:Name="PART_ControlsLayer" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="{Binding FunctionName, FallbackValue=Functionname}" />
<Button Grid.Row="1" Content="{Binding DisplayValue, FallbackValue=0.5dB}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CustomControl2}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl x:Name="PART_DisplayLayer" Style="{StaticResource DefaultTemplate}" />
<Grid x:Name="PART_ControlsLayer" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button Grid.Row="0" Content="Depp" />
<Button Grid.Row="1" Content="{Binding DisplayValue, FallbackValue=1.0dB}" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
CustomControl2.cs as Derived-Class
using System.Windows;
namespace WpfCustomControlLibrary1
{
public class CustomControl2 : CustomControl1
{
static CustomControl2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl2), new FrameworkPropertyMetadata(typeof(CustomControl2)));
}
public CustomControl2()
{
DataContext = this;
}
}
}
MainWindow still the same.
This is working since nothing of content is static.
Related
In a WPF application I have a style used for buttons:
<Style TargetType="Button" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="/MyProject;component/Images/ButtonEllipse.png"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
For most buttons that is OK but for one specific instance I want to use the same template but change the image to ButtonEllipseNew.png (which is the value of a view model property). The button is defined like this:
<Button Content="Test" Style="{StaticResource ButtonEllipse}">
</Button>
How can I change the value of the image source in the template only for this specific button? I want to bind the source to a property in the view model.
I am afraid you can't reuse only a part of a ControlTemplate. You must define the template as a whole:
WPF: Is there a way to override part of a ControlTemplate without redefining the whole style?
What you could do is to bind the Source property of the Image in the template to some property that you can then set individually for each control to which the template is applied:
<Style TargetType="Button" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None"
Source="{Binding Tag, RelativeSource={RelativeSource AncestorType=Button}}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
...
<Button Content="Test" Style="{StaticResource ButtonEllipse}" Tag="image.png" />
I would write a custom control that looks something like this:
internal class IconButton : Button
{
public ImageSource Source
{
get { return (ImageSource)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(ImageSource), typeof(IconButton), new PropertyMetadata(null));
}
Then edit your style to accommodate it:
<Style TargetType="{x:Type location:IconButton}" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type location:IconButton}">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="{TemplateBinding Source"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
Where location is a xaml-defined namespace where the IconButton class is placed.
Then just set the Source property of your button. You can mess around with the Source property to set a default as well.
In order to bind a property to your style you should do the following:
Create a user control let's name it ButtonImage.cs
public class ButtonImage : Button
{
public ImageSource Source
{
get
{
return (ImageSource)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source",
typeof(ImageSource),
typeof(ButtonImage),
new PropertyMetadata(null));
}
I would create a ResourceDictionary so you can use it for all your styles. Let's name it Dictionary.xaml. An there you define your style like:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4">
<Style TargetType="{x:Type local:ButtonImage}" x:Key="ButtonEllipse">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonImage}">
<StackPanel Orientation="Vertical">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0 0 0 10"/>
<Image x:Name="ButtonImage" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None" Source="{TemplateBinding Source}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Then in your view you can do:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApplication4="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="Dictionary.xaml">
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<wpfApplication4:ButtonImage Margin="0,50,0,0" Content="Test" Source="{Binding Name}" Style="{StaticResource ButtonEllipse}" />
</Grid>
</Window>
I have a custom WindowStyle, the XAML looks like this:
<Style TargetType="{x:Type Window}"
x:Key="WindowStyle">
/** Some setters **/
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<AdornerDecorator>
<Grid Background="#88000000"
x:Name="WindowBackgroundGrid">
<Border x:Name="WindowContentBorder"
Background="{DynamicResource WindowBackground}"MaxHeight="{Binding Source={x:Static SystemParameters.FullPrimaryScreenHeight}}"
MaxWidth="{Binding Source={x:Static SystemParameters.FullPrimaryScreenWidth}}"
Margin="20">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<Border BorderBrush="{DynamicResource BorderBrushColor}"
Background="{DynamicResource PaneHeader_Background}"
Grid.Row="0">
<TextBlock Text="Title"Foreground="{DynamicResource DefaultForeground}"
FontSize="16"
FontWeight="Bold"
Margin="5,5,2,5" />
</Border>
<!-- Content -->
<ScrollViewer Grid.Row="1"
Margin="5">
<ContentPresenter Content="{TemplateBinding Content}" />
</ScrollViewer>
</Grid>
</Border>
</Grid>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now I want the inner Grid in a seperate Style so that I can use it elsewhere.
<Style x:Key="WindowContentStyle"
TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
/** Border control **/
<!-- Content -->
<ScrollViewer Grid.Row="1"
Margin="5">
<ContentPresenter Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</ScrollViewer>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
And I use a ContenPresenter in my WindowStyle to present it:
<ContentPresenter>
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}"
BasedOn="{StaticResource WindowContentStyle}" />
</ContentPresenter.Style>
</ContentPresenter>
Problem
The edit above didn't give me any errors, but it doesn't present my WindowContentStyle.
When I set the Content property of the Window control and load the style
this.window.Content = view;
this.window.Style = (Style)Application.Current.TryFindResource("WindowStyle");
the content is shown in the ContentPresenter in the WindowStyle and not in WindowContentStyle. Because of this, the Template is not used and I don't have a header with a title.
How can I make my outer ContentPresenter pass on the Content to my inner ContentPresenter (the one in WindowContentStyle)?
Thanks in advance!
Greetings
Loetn
You should use a ContentControl to display your content, not a ContentPresenter. From the ContentPresenter Class page on MSDN:
You typically use the ContentPresenter in the ControlTemplate of a ContentControl to specify where the content is to be added.
From the ContentControl Class page on MSDN:
A ContentControl has a limited default style. If you want to enhance the appearance of the control, you can create a new DataTemplate.
I have a textblock as mentioned in code below:
<TextBlock Grid.Row=........
.................
Grid.RowSpan="{Binding RowSp}"
HorizontalAlignment="Left" />
Now I want to check if RowSpan > 1 then I want to add a space and a Colon at the end of the text.I think(not tried) I have got it using Triggers and Converter as mentioned in Code below:
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="{Binding RowSp,
Converter={StaticResource colonAlignmentConverter}}"
Value="True" >
<Setter Property="Text"
Value="{Binding Txt,
Converter=ColonAlignmentConverter}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
public class ColonAlignmentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (int)value > 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Now the problem is here:
If RowSpan = 1 then I want to add colon(:) in the same Grid.Row and Grid.Column but I want it right aligned. How can I achieve this?
I can use another textblock for colon if it is possible.
This would usually be done with templates but, as you already know, you cannot template a TextBlock.
However, when you pass a string to the Content property of a ContentControl, the ContentControl uses a TextBlock to display the string. This was discovered by using Snoop, an awesome WPF debugging tool.
Taking this into account, the fact that Grid.RowSpan for any child of a Grid has a default value of 1, and that you want to display a colon whenever the TextBlock has a RowSpan greater than 1, the following will do what you want.
<Window x:Class="StackOverflow._20820850.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="TextBlockContentControlStyle" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Grid>
<TextBlock Text="{TemplateBinding Content}" />
<TextBlock Text=":" HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Grid.RowSpan" Value="1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Grid>
<TextBlock Text="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ContentControl Content="One" Style="{StaticResource TextBlockContentControlStyle}" />
<ContentControl Content="Two" Grid.Column="1" Grid.RowSpan="2" Style="{StaticResource TextBlockContentControlStyle}" />
</Grid>
</Window>
Things to note
I have replaced the TextBlock with a ContentControl (I am not sure if this option is available to you)
I wanted to use a pure XAML approach so my default template is the template that manages RowSpan > 1. I then supply a trigger to set the template back to the default RowSpan = 1.
This needs to be a keyed style as setting this as a default style will probably break every other control that utilises the ContentControl in its Logical or Visual tree.
If you do not want to use a ContentControl, another approach would be to create your own UserControl. In the following, I have created a control called MyTextBlock (I could have called it TextBlock and let the namespacing sort it out but this is clearer in an example) and I then Templated the content of the Control. The XAML of the user control is
<UserControl x:Name="ThisControl" x:Class="StackOverflow._20820850.MyTextBlock" 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="300" d:DesignWidth="300">
<UserControl.ContentTemplate>
<DataTemplate>
<Grid DataContext="{Binding ElementName=ThisControl}">
<TextBlock Text="{Binding Path=Content}" />
<TextBlock Text=":" HorizontalAlignment="Right">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding (Grid.RowSpan)}" Value="1">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</UserControl.ContentTemplate>
</UserControl>
And the grid in the Window now looks like
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<this:MyTextBlock Content="One" />
<this:MyTextBlock Content="Two" Grid.Column="1" Grid.RowSpan="2" />
</Grid>
Obviously, the colon can be missed as it is pushed right up against the right hand side of the control but I will leave formatting to you.
I hope this helps.
I'm trying to create a user control that has a Grid with two rows.
the first row for a title and the second one for a content that will be defined outside the user control such as a Button in our example.
Somehow I didn't get it to work.
UserControl1 xaml:
<Grid Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Title" FontSize="30" Margin="10,0,0,0"/>
</Grid>
MainWindow xaml:
<Grid>
<local:UserControl1>
<Button>Click me</Button>
</local:UserControl1>
</Grid>
The picture below should explain what's my problem:
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>
You have to set the ControlTemplate:
<UserControl>
<UserControl.Resources>
<Style TargetType="{x:Type local:UserControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:UserControl1}">
<Grid Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Title" FontSize="30" Margin="10,0,0,0"/>
<ContentPresenter Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
You can template the user control to add additional visuals like the TextBlock.
<UserControl>
<UserControl.Style>
<Style TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Title" FontSize="30" Margin="10,0,0,0"/>
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Style>
<Button>
Click me!
</Button>
</UserControl>
Use template with
< ContentControl />
Instead of using Content Presenter
So place this:
<UserControl.Style>
<Style TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}" >
<Grid Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Title" FontSize="30" Margin="10,0,0,0"/>
<ContentControl Grid.Row="1" Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Style>
to your userControl
This is the simple general template for a user control (without using styles or properties to set the content):
<UserControl ...>
<UserControl.Template>
<ControlTemplate TargetType="{x:Type UserControl}">
<!-- control contents here -->
<ContentPresenter/><!-- outside contents go here -->
<!-- control contents here -->
</ControlTemplate>
</UserControl.Template>
</UserControl>
The <ControlTemplate> represents the user control's XAML duplicated for each control.
The <ContentPresenter> is where the Content gets put when consuming the control.
I'm creating my own control, I'm wondering the best way to bind the textblock text property below, I have the contentpresenter set to bind to content, how can I set the textblock text value?
<Style TargetType="ctrl:Selection">
<Setter Property="Width" Value="200" />
<Setter Property="Height" Value="100" />
<Setter Property="Background" Value="Lavender" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:Selection">
<Grid x:Name="RootElement">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Background}" Stroke="black" RadiusX="16" RadiusY="16" />
<TextBlock Grid.Row="0" Text="How do i bind this?" x:Name="HeaderText" HorizontalAlignment="Center" VerticalAlignment="Center" />
<ContentPresenter Grid.Row="1" x:Name="BodyContent" Content="{TemplateBinding Content}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class Selection : ContentControl {
public Selection() {
this.DefaultStyleKey = typeof(Selection);
}
}
You can register a property in your control class, something like this...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(Selection), new PropertyMetadata(""));
public string Text {
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}