Click here to Skip to main content
15,897,891 members
Articles / Desktop Programming / WPF

Customizing WPF Expander with ControlTemplate

Rate me:
Please Sign up or sign in to vote.
4.91/5 (94 votes)
8 Sep 2013CPOL8 min read 363.2K   16.1K   150  
Talks about how to customize the look and behaviour of WPF Expander by templating it.
<Window x:Class="SampleExpander.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local ="clr-namespace:SampleExpander"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Width="300"
        Height="550"
        Title="MainWindow">
    <Window.Resources>

        <!-- Simple Expander Button's Template-->
        <ControlTemplate x:Key="SimpleExpanderButtonTemp" TargetType="{x:Type ToggleButton}">
            <Border x:Name="ExpanderButtonBorder"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}"
                    >
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Rectangle Fill="Transparent"
                               Grid.ColumnSpan="2"/>
                    <Ellipse Name="Circle"
                         Grid.Column="0"
                         Stroke="DarkGray"
                         Width="20"
                         Height="20"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Center"
                         />
                    <Path x:Name="Sign"
                      Grid.Column="0"
                      Data="M 0,5 H 10 M 5,0 V 10 Z"
                      Stroke="#FF666666"
                      Width="10"
                      Height="10"
                      StrokeThickness="2"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      RenderTransformOrigin="0.5,0.5"
                      />
                    <ContentPresenter x:Name="HeaderContent"
                                  Grid.Column="1"
                                  Margin="4,0,0,0"
                                  ContentSource="Content"/>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <!--Change the sign to minus when toggled-->
                <Trigger Property="IsChecked"
                         Value="True">
                    <Setter Property="Data" TargetName="Sign" Value="M 0,5 H 10 Z"/>
                </Trigger>

                <!-- MouseOver, Pressed behaviours-->
                <Trigger Property="IsMouseOver"
                                 Value="true">
                    <Setter Property="Stroke"
                                    Value="#FF3C7FB1"
                                    TargetName="Circle"/>
                    <Setter Property="Stroke"
                                    Value="#222"
                                    TargetName="Sign"/>
                </Trigger>
                <Trigger Property="IsPressed"
                                 Value="true">
                    <Setter Property="Stroke"
                                    Value="#FF526C7B"
                                    TargetName="Circle"/>
                    <Setter Property="StrokeThickness"
                                    Value="1.5"
                                    TargetName="Circle"/>
                    <Setter Property="Stroke"
                                    Value="#FF003366"
                                    TargetName="Sign"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- Simple Expander Template-->
        <ControlTemplate x:Key="SimpleExpanderTemp" TargetType="{x:Type Expander}">
            <DockPanel>
                <ToggleButton x:Name="ExpanderButton" 
                              DockPanel.Dock="Top"
                              Template="{StaticResource SimpleExpanderButtonTemp}"
                              Content="{TemplateBinding Header}"
                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                              OverridesDefaultStyle="True"
                              Padding="1.5,0">
                </ToggleButton>
                <ContentPresenter x:Name="ExpanderContent"
                                  Grid.Row="1" 
                                  Visibility="Collapsed"
                                  DockPanel.Dock="Bottom"/>
            </DockPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsExpanded" Value="True">
                    <Setter TargetName="ExpanderContent" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- Animated Expander Button's Template-->
        <ControlTemplate x:Key="AnimatedExpanderButtonTemp" TargetType="{x:Type ToggleButton}">
            <Border x:Name="ExpanderButtonBorder"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Padding="{TemplateBinding Padding}"
                    >
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Rectangle Fill="Transparent"
                               Grid.ColumnSpan="2"/>
                    <Ellipse Name="Circle"
                         Grid.Column="0"
                         Stroke="DarkGray"
                         Width="20"
                         Height="20"
                         HorizontalAlignment="Center"
                         VerticalAlignment="Center"
                         />
                    <Path x:Name="Arrow"
                      Grid.Column="0"
                      Data="M 1,1.5 L 4.5,5 8,1.5"
                      Stroke="#FF666666"
                      StrokeThickness="2"
                      HorizontalAlignment="Center"
                      VerticalAlignment="Center"
                      RenderTransformOrigin="0.5,0.5"
                      >
                        <Path.RenderTransform>
                            <RotateTransform Angle="0"/>
                        </Path.RenderTransform>
                    </Path>
                    <ContentPresenter x:Name="HeaderContent"
                                      Grid.Column="1"
                                      Margin="4,0,0,0"
                                      ContentSource="Content"/>
                </Grid>
            </Border>
            <ControlTemplate.Triggers>
                <!-- Animate arrow when toggled-->
                <Trigger Property="IsChecked"
                         Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="Arrow"
                                                 Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                                 To="180"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="Arrow"
                                                 Storyboard.TargetProperty="(Path.RenderTransform).(RotateTransform.Angle)"
                                                 To="0"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>

                <!-- MouseOver, Pressed behaviours-->
                <Trigger Property="IsMouseOver"
                                 Value="true">
                    <Setter Property="Stroke"
                                    Value="#FF3C7FB1"
                                    TargetName="Circle"/>
                    <Setter Property="Stroke"
                                    Value="#222"
                                    TargetName="Arrow"/>
                </Trigger>
                <Trigger Property="IsPressed"
                                 Value="true">
                    <Setter Property="Stroke"
                                    Value="#FF526C7B"
                                    TargetName="Circle"/>
                    <Setter Property="StrokeThickness"
                                    Value="1.5"
                                    TargetName="Circle"/>
                    <Setter Property="Stroke"
                                    Value="#FF003366"
                                    TargetName="Arrow"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
        
        <!-- Stretch Out Content Expander's Template,
        Uses: AnimatedExpanderButtonTemp from above-->
        <ControlTemplate x:Key="StretchyExpanderTemp" TargetType="{x:Type Expander}">
            <DockPanel>
                <ToggleButton x:Name="ExpanderButton" 
                              DockPanel.Dock="Top"
                              Template="{StaticResource AnimatedExpanderButtonTemp}"
                              Content="{TemplateBinding Header}"
                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                              OverridesDefaultStyle="True"
                              Padding="1.5,0">
                </ToggleButton>
                <ContentPresenter x:Name="ExpanderContent"
                                  ContentSource="Content"
                                  DockPanel.Dock="Bottom"
                                  >
                    <ContentPresenter.LayoutTransform>
                        <ScaleTransform ScaleY="0"/>
                    </ContentPresenter.LayoutTransform>
                </ContentPresenter>
            </DockPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsExpanded" Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="ExpanderContent"
                                                 Storyboard.TargetProperty="(ContentPresenter.LayoutTransform).(ScaleTransform.ScaleY)"                                                 
                                                 To="1"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="ExpanderContent"
                                                 Storyboard.TargetProperty="(ContentPresenter.LayoutTransform).(ScaleTransform.ScaleY)"
                                                 To="0"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>

            </ControlTemplate.Triggers>
        </ControlTemplate>

        <!-- Slide Out Content Expander's Template, 
        Uses: AnimatedExpanderButtonTemp from above, 
              MultiplyConverter in codebehind-->
        <local:MultiplyConverter x:Key="multiplyConverter" />
        <ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
            <DockPanel>
                <ToggleButton x:Name="ExpanderButton" 
                              DockPanel.Dock="Top"
                              Template="{StaticResource AnimatedExpanderButtonTemp}"
                              Content="{TemplateBinding Header}"
                              IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
                              OverridesDefaultStyle="True"
                              Padding="1.5,0">
                </ToggleButton>
                <ScrollViewer x:Name="ExpanderContentScrollView" DockPanel.Dock="Bottom"
                              HorizontalScrollBarVisibility="Hidden"
                              VerticalScrollBarVisibility="Hidden"
                              HorizontalContentAlignment="Stretch"
                              VerticalContentAlignment="Bottom"
                              >
                    <ScrollViewer.Tag>
                        <sys:Double>0.0</sys:Double>
                    </ScrollViewer.Tag>
                    <ScrollViewer.Height>
                        <MultiBinding Converter="{StaticResource multiplyConverter}">
                            <Binding Path="ActualHeight" ElementName="ExpanderContent"/>
                            <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                        </MultiBinding>
                    </ScrollViewer.Height>
                    <ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
                </ScrollViewer>
            </DockPanel>
            <ControlTemplate.Triggers>
                <Trigger Property="IsExpanded" Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                                 Storyboard.TargetProperty="Tag"                                                 
                                                 To="1"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
                                                 Storyboard.TargetProperty="Tag"                                                 
                                                 To="0"
                                                 Duration="0:0:0.4"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Window.Resources>

    <!-- Small demo of each Expander Template above-->
    <StackPanel>
        <StackPanel.Resources>
            <Style x:Key="TestContentStyle" TargetType="Button">
                <Setter Property="Content" Value="Test Button"/>
                <Setter Property="MinWidth" Value="100"/>
                <Setter Property="MinHeight" Value="100"/>
            </Style>
        </StackPanel.Resources>
        
        <Expander Header="Using Default Expander"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top">
            <Button Style="{StaticResource TestContentStyle}"/>
        </Expander>
        
        <Expander Template="{StaticResource SimpleExpanderTemp}"
                  OverridesDefaultStyle="True"
                  Header="Using SimpleExpanderTemp"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top"
                  >
            <Button Style="{StaticResource TestContentStyle}"/>
        </Expander>
        

        
        <Expander Template="{StaticResource StretchyExpanderTemp}"
                  OverridesDefaultStyle="True"
                  Header="Using StretchyExpanderTemp"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top"
                  >
            <Button Style="{StaticResource TestContentStyle}"/>
        </Expander>
        <Expander Template="{StaticResource RevealExpanderTemp}"
                  OverridesDefaultStyle="True"
                  Header="Using RevealExpanderTemp"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top"
                  >
            <Button Style="{StaticResource TestContentStyle}"/>
        </Expander>
        
    </StackPanel>
</Window>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Canada Canada

Comments and Discussions