Reputation: 1152
I have a UserControl which has a button inside it. This button has a personalized style as bellow. I want to create a property in my UserControl that affects the CornerRadius of a border called "Background" which is inside the button template, so that I can make the button corner round when needed.
I tried to create a property in my usercontrol and use OnApplyTemplate event and GetTemplateChild method but didn't work. I found the border, but nothing happens.
public override void OnApplyTemplate()
{
Border border = GetTemplateChild("Background") as Border;
border.CornerRadius = this.CornerRadius;
}
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
<ColorAnimation Duration="0" To="#8CFFFFFF" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0" Background="Transparent" />
<Grid Background="{TemplateBinding Background}" Margin="1">
<Border x:Name="BackgroundAnimation" Background="Transparent" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient" Fill="Transparent" />
</Grid>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="0" RadiusX="0"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusY="0" RadiusX="0" Stroke="#FF6DBDD1" StrokeThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit - Solution
I got what I wanted by creating a heritage of Button with a dependency property for CornerRadius, then using TemplateBinding.
Important XAML parts:
<Style x:Key="ButtonStyle" TargetType="myProject:MyButton">
<ControlTemplate TargetType="myProject:MyButton">
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}">
Full XAML and C#
public class MyButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
typeof(MyButton), new FrameworkPropertyMetadata(new CornerRadius(0, 0, 0, 0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
}
<Style x:Key="ButtonStyle" TargetType="myProject:MyButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="myProject:MyButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
<ColorAnimation Duration="0" To="#8CFFFFFF" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}">
<Grid>
<Border x:Name="BackgroundAnimation" Background="Transparent" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient" Fill="Transparent"/>
</Grid>
</Border>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="{Binding ElementName=Background, Path=CornerRadius.TopLeft}" RadiusX="{Binding ElementName=Background, Path=CornerRadius.BottomLeft}"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusX="0" RadiusY="0" Stroke="#FF6DBDD1" StrokeThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Upvotes: 1
Views: 3495
Reputation: 3797
The best way to do that through Template Binding. WPF best practices say that you need to apply UI changes through appropriate WPF mechanism: XAML. Consequently, we need to change our Style or Template. If it is possible, we should create a flexible template to allow to change UI by changing of style, creation a new Style without modification of the Template.
Open/Closed SOLID
principle: Extend and not modify.
Unfortunately, we forgot that CornerRadius
it is the attached property of class Border
.
So we can create a Template Binding:
<ControlTemplate TargetType="{x:Type Button}"
x:Key="ControlTemplateButtonNormal">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding Border.CornerRadius}"
x:Name="BorderRoot">
<Grid>
<ContentPresenter IsTabStop="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
<Trigger Property="Validation.HasError"
Value="True">
<Setter Property="Visibility"
TargetName="ErrorElement"
Value="Visible" />
<Setter Property="BorderBrush"
TargetName="BorderRoot"
Value="Transparent" />
</Trigger>
<Trigger Property="IsReadOnly"
Value="True">
<Setter Property="Background"
TargetName="BorderRoot"
Value="{StaticResource BorderBrushReadonly}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Now we can use this template to create different Styles. Normal button:
<Style TargetType="{x:Type Button}"
x:Key="ButtonNormalStyle">
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="Border.CornerRadius"
Value="0" />
<Setter Property="Template"
Value="{StaticResource ControlTemplateButtonNormal}" />
</Style>
Button Round:
<Style TargetType="{x:Type Button}"
x:Key="ButtonRoundStyle">
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="Border.CornerRadius"
Value="5,5,5,5" />
<Setter Property="Template"
Value="{StaticResource ControlTemplateButtonNormal}" />
</Style>
I hope it will be useful.
Upvotes: 2
Reputation: 12533
You need to search the VisualTree for the border. it's not part of the UserControl's template so it's not a Template child.
Here's some good extensions to help you with this and other situations where you need to traverse the VisualTree :
public static class VisualTreeHelperExtensions
{
public static T FindVisualParent<T>(DependencyObject depObj) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(depObj);
if (parent == null || parent is T)
return (T)parent;
return FindVisualParent<T>(parent);
}
public static T FindVisualChild<T>(DependencyObject depObj) where T : Visual
{
if (depObj != null && IsVisual(depObj))
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
return childOfChild;
}
}
}
return null;
}
public static T FindVisualChild<T>(DependencyObject depObj, string name) where T : FrameworkElement
{
if (depObj != null && IsVisual(depObj))
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T && (child as T).Name.Equals(name))
{
return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
if (childOfChild.Name.Equals(name))
return childOfChild;
}
}
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
yield break;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
if (IsVisual(depObj))
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T) child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
private static bool IsVisual(DependencyObject depObj)
{
return depObj is Visual || depObj is Visual3D;
}
}
In this case use :
public override void OnApplyTemplate()
{
Border border = VisualTreeHelperExtensions.FindVisualChild<Border>(this,"Background") as Border;
border.CornerRadius = this.CornerRadius;
}
Upvotes: 1
Reputation: 10764
If you have a property called CornerRadius
in your control, I'm pretty sure this would do it:
<Border CornerRadius="{TemplateBinding CornerRadius}"/>
Upvotes: 1