1
votes

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>
3

3 Answers

1
votes

If you have a property called CornerRadius in your control, I'm pretty sure this would do it:

<Border CornerRadius="{TemplateBinding CornerRadius}"/>
1
votes

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;
}
1
votes

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.