User Tools

Site Tools


notes:uwp:controltemplate

ControlTemplate in XAML

  • Control templates specify the visual structure and behavior of a control.
  • Defining a new template for a control replaces the existing template completely.
  • The control template can have only one element (of type FrameworkElement) as its root.
  • The ContentPresenter is a light-weight element that was designed specifically for use in control templates.

If you need programmatic access to a named element inside a template, use the templates FindName method after the template has been applied to a target.

There are two ways of obtaining templates of built-in controls:

  • Using Visual Studio (or Blend): right-click a control in the XAML designer > Edit Template > Edit a Copy.
  • Go to \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK version>\Generic\generic.xaml, for example: C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.14393.0\Generic\generic.xaml

Example: A simple template for a button:

<Button Content="Test" Background="Bisque">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border Width="200" Height="100" CornerRadius="20"
                    Background="{TemplateBinding Background}">
                <ContentControl Content="{TemplateBinding Content}"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center" />
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>

Example: A template with ContentPresenter:

<Page.Resources>
    <Style x:Key="CustomButtonStyle" TargetType="Button">
        <Setter Property="Margin" Value="10" />
        <Setter Property="FontSize" Value="30" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Border BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Background="{TemplateBinding Background}"
                            CornerRadius="10">
                        <!-- Bind the button's Padding to the ContentPresenter's Margin -->
                        <!-- TemplateBinding for Content and ContentTemplate is optional 
                             as it is provided by default -->
                        <ContentPresenter 
                            Margin="{TemplateBinding Padding}"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>
 
<Button Content="Click me!"
    Style="{StaticResource CustomButtonStyle}"
    BorderBrush="Green"
    BorderThickness="5"
    Padding="20" />

Example: A template for a ProgressBar control. It uses the names DeterminateRoot and ProgressBarIndicator for its elements. These elements are recognized by the underlying code of the ProgressBar control:

<Page.Resources>
    <ControlTemplate x:Key="ProgressBarCustomTemplate" TargetType="ProgressBar">
        <Border Height="100"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}"
                Background="White">
 
            <!-- DeterminateRoot and its child ProgressBarIndicator -->
            <Border x:Name="DeterminateRoot" Margin="100,0">
                <Rectangle x:Name="ProgressBarIndicator" Fill="Blue" />
            </Border>
        </Border>
    </ControlTemplate>
</Page.Resources>
 
<StackPanel>
    <Slider x:Name="Slider1" />
    <ProgressBar BorderBrush="Red" BorderThickness="2"
                 Value="{Binding ElementName=Slider1, Path=Value}"
                 Template="{StaticResource ProgressBarCustomTemplate}" />
</StackPanel>

TemplateBinding

TemplateBinding enables properties of the control to act as parameters to the template. In order to find out what template bindings may be needed, check the default template of a control.

TemplateBinding is a shortcut for a RelativeSource binding. As TemplateBinding is one-way only, the use of RelativeSource instead allows you to specify a Mode parameter.

<!-- using TemplateBinding -->
<ControlTemplate>
    <Border Margin="{TemplateBinding Margin}">
    ...
    </Border>
</ControlTemplate>
 
<!-- using RelativeSource - we can specify the Mode parameter -->
<ControlTemplate>
    <Border Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Margin, Mode=TwoWay}">
    ...
    </Border>
</ControlTemplate>

TemplateSettings

TemplateSettings are calculated values that help templates provide the desired results because they can't do the same calculations in XAML.

VisualStates

Example: A button with all built-in behaviours removed:

<Page x:Class="TestApp.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="PointOver" />
                                    <VisualState x:Name="Pressed" />
                                    <VisualState x:Name="Disabled" />
                                </VisualStateGroup>
 
                                <VisualStateGroup x:Name="FocusStates">
                                    <VisualState x:Name="Unfocused" />
                                    <VisualState x:Name="Focused" />
                                    <VisualState x:Name="PointerFocused" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
 
                            <Border BorderBrush="Blue" BorderThickness="2">
                                <ContentPresenter Margin="{TemplateBinding Padding}" />
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Content="Test Button" Padding="12" />
    </Grid>
</Page>

Example: A button with custom VisualStates:

<Page x:Class="TestApp.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid RenderTransformOrigin=".5,.5">
                            <VisualStateManager.VisualStateGroups>
 
                                <!-- CommonStates group -->
                                <VisualStateGroup x:Name="CommonStates">
 
                                    <!-- Transitions for the CommonStates group -->
                                    <VisualStateGroup.Transitions>
                                        <!-- 
                                        Manually handle the transition to/from PointerOver 
                                        because an explicit dependent animation is needed.
                                        VisualTransition.Storyboard offers the higest level of flexibility. 
                                        It allows you to provide your own animation.
                                        -->
                                        <VisualTransition To="PointerOver">
                                            <VisualTransition.Storyboard>
                                                <Storyboard TargetName="GlowGradientStop" TargetProperty="Color">
                                                    <ColorAnimation To="Yellow" Duration="0:0:.2"
                                                                    EnableDependentAnimation="True" />
                                                </Storyboard>
                                            </VisualTransition.Storyboard>
                                        </VisualTransition>
                                        <VisualTransition From="PointerOver">
                                            <VisualTransition.Storyboard>
                                                <Storyboard TargetName="GlowGradientStop" TargetProperty="Color">
                                                    <ColorAnimation To="Red" Duration="0:0:.8"
                                                                    EnableDependentAnimation="True" />
                                                </Storyboard>
                                            </VisualTransition.Storyboard>
                                        </VisualTransition>
 
                                        <!-- Make transitions to/from Pressed instantaneous -->
                                        <!-- 
                                        GeneratedDuration controls the duration of the generated linear animation.
                                        There is also GeneratedEasingFunction which sets a nonlinear animation
                                        between states.
                                        -->
                                        <VisualTransition To="Pressed" GeneratedDuration="0" />
                                        <VisualTransition From="Pressed" To="PointerOver" GeneratedDuration="0" />
                                    </VisualStateGroup.Transitions>
 
                                    <!-- States for the CommonStates group -->
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="PointerOver">
                                        <!-- Keep the yellow tint when hovering over the button -->
                                        <VisualState.Setters>
                                            <Setter Target="GlowGradientStop.Color" Value="Yellow" />
                                        </VisualState.Setters>
                                    </VisualState>
 
                                    <VisualState x:Name="Pressed">
                                        <VisualState.Setters>
                                            <!-- Shrink the button a little bit -->
                                            <Setter Target="RootTransform.ScaleX" Value="0.9" />
                                            <Setter Target="RootTransform.ScaleY" Value="0.9" />
 
                                            <!-- Keep the yellow tint when hovering over the button -->
                                            <Setter Target="GlowGradientStop.Color" Value="Yellow" />
                                        </VisualState.Setters>
                                    </VisualState>
 
                                    <VisualState x:Name="Disabled">
                                        <VisualState.Setters>
                                            <Setter Target="GlowGradientStop.Color" Value="Gray" />
                                        </VisualState.Setters>
                                    </VisualState>
                                </VisualStateGroup>
 
                                <!-- FocusStates group -->
                                <VisualStateGroup x:Name="FocusStates">
                                    <VisualState x:Name="Focused" />
                                    <VisualState x:Name="Unfocused" />
                                    <VisualState x:Name="PointerFocused" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
 
                            <!-- RootTransform is used in the Pressed animation to shrink a button when 
                                 it's clicked. -->
                            <Grid.RenderTransform>
                                <CompositeTransform x:Name="RootTransform" />
                            </Grid.RenderTransform>
 
                            <!-- Button's visual elements -->
 
                            <Border CornerRadius="25">
                                <Border.Background>
                                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                        <!-- We are using the more elaborate syntax rather than TemplateBinding
                                             because we need to access the Background.Color path instead of just 
                                             a property. -->
                                        <GradientStop Offset="0" 
                       Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background.Color}" />
 
                                        <!-- The lower part of the button. Animations reference it. -->
                                        <GradientStop x:Name="GlowGradientStop" Offset="1" Color="Red" />
                                    </LinearGradientBrush>
                                </Border.Background>
                            </Border>
                            <Viewbox Width="100" Height="100">
                                <ContentPresenter Margin="{TemplateBinding Padding}" />
                            </Viewbox>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Content="Test Button" 
                Background="Blue"
                Padding="12"
                Foreground="White"
                HorizontalAlignment="Center" 
                VerticalAlignment="Center" />
    </Grid>
</Page>

Example: Make a button jump continuosly when it acquires focus. When it loses focus, undo the jumping animation smoothly:

<Page x:Class="TestApp.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 
    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
 
                                <!-- CommonStates group -->
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="PointerOver" />
                                    <VisualState x:Name="Pressed" />
                                    <VisualState x:Name="Disabled" />
                                </VisualStateGroup>
 
                                <!-- FocusStates group -->
                                <VisualStateGroup x:Name="FocusStates">
 
                                    <!-- Transitions for the FocusStates group -->
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition From="Focused">
                                            <VisualTransition.Storyboard>
                                                <!-- Undo the Focused animation smoothly -->
                                                <Storyboard TargetName="RootTransform" TargetProperty="TranslateY">
                                                    <DoubleAnimation To="0" Duration="0:0:.4">
                                                        <DoubleAnimation.EasingFunction>
                                                            <QuadraticEase />
                                                        </DoubleAnimation.EasingFunction>
                                                    </DoubleAnimation>
                                                </Storyboard>
                                            </VisualTransition.Storyboard>
                                        </VisualTransition>
                                    </VisualStateGroup.Transitions>
 
                                    <!-- States for the FocusStates group -->
                                    <VisualState x:Name="Unfocused" />
 
                                    <VisualState x:Name="Focused">
                                        <!-- A continuous "jumping" animation -->
                                        <Storyboard TargetName="RootTransform" TargetProperty="TranslateY">
                                            <DoubleAnimation To="-20" RepeatBehavior="Forever" 
                                                             AutoReverse="True" Duration="0:0:.4">
                                                <DoubleAnimation.EasingFunction>
                                                    <QuadraticEase />
                                                </DoubleAnimation.EasingFunction>
                                            </DoubleAnimation>
                                        </Storyboard>
                                    </VisualState>
 
                                    <VisualState x:Name="PointerFocused" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
 
                            <Grid.RenderTransform>
                                <CompositeTransform x:Name="RootTransform" />
                            </Grid.RenderTransform>
 
                            <!-- Button's visual elements -->
                            <Border BorderBrush="Blue" BorderThickness="2">
                                <ContentPresenter Margin="{TemplateBinding Padding}" />
                            </Border>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Page.Resources>
    <Grid>
        <Button Content="Test Button" Padding="12" />
    </Grid>
</Page>

Animations generated by VisualTransitions are independent only. This means that if you change a color in one VisualState then apply a VisualTransition with a GeneratedDuration longer than zero, it will have no effect on the color change. It will still happen instantaneously. The way to make this work is to give the relevant state transitions an explicit VisualTransition whose Storyboard property is set to a Storyboard containing an explicitly dependent animation.

Within each group, the states are mutually exclusive. If a control does not use certain states, it should still include them (as empty tags). Otherwise, the transition to that state won't occur.

notes/uwp/controltemplate.txt · Last modified: 2017/02/28 by admin