Animate Expander in WPF

2019-01-18 09:02发布

问题:

How to animate the expanded and collapsed actions of a Wpf expander control?

回答1:

I've found this article on Code Project : Read Here

The author creates his own SimpleExpander Template, then adds a stretch out animation to it. He even added a nifty rotating arrow to it.



回答2:

I created a style based on msdn Style and the point in this answer:

 <Window x:Class="Test.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:local="clr-namespace:Test" 
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    mc:Ignorable="d">

<Window.Resources>
    <Color x:Key="WindowColor">#FFE8EDF9</Color>
    <Color x:Key="ContentAreaColorLight">#FFC5CBF9</Color>
    <Color x:Key="ContentAreaColorDark">#FF7381F9</Color>

    <Color x:Key="DisabledControlLightColor">#FFE8EDF9</Color>
    <Color x:Key="DisabledControlDarkColor">#FFC5CBF9</Color>
    <Color x:Key="DisabledForegroundColor">#FF888888</Color>

    <Color x:Key="SelectedBackgroundColor">#FFC5CBF9</Color>
    <Color x:Key="SelectedUnfocusedColor">#FFDDDDDD</Color>

    <Color x:Key="ControlLightColor">White</Color>
    <Color x:Key="ControlMediumColor">#FF7381F9</Color>
    <Color x:Key="ControlDarkColor">#FF211AA9</Color>

    <Color x:Key="ControlMouseOverColor">#FF3843C4</Color>
    <Color x:Key="ControlPressedColor">#FF211AA9</Color>


    <Color x:Key="GlyphColor">#FF444444</Color>
    <Color x:Key="GlyphMouseOver">sc#1, 0.004391443, 0.002428215, 0.242281124</Color>

    <!--Border colors-->
    <Color x:Key="BorderLightColor">#FFCCCCCC</Color>
    <Color x:Key="BorderMediumColor">#FF888888</Color>
    <Color x:Key="BorderDarkColor">#FF444444</Color>

    <Color x:Key="PressedBorderLightColor">#FF888888</Color>
    <Color x:Key="PressedBorderDarkColor">#FF444444</Color>

    <Color x:Key="DisabledBorderLightColor">#FFAAAAAA</Color>
    <Color x:Key="DisabledBorderDarkColor">#FF888888</Color>

    <Color x:Key="DefaultBorderBrushDarkColor">Black</Color>

    <!--Control-specific resources.-->
    <Color x:Key="HeaderTopColor">#FFC5CBF9</Color>
    <Color x:Key="DatagridCurrentCellBorderColor">Black</Color>
    <Color x:Key="SliderTrackDarkColor">#FFC5CBF9</Color>

    <Color x:Key="NavButtonFrameColor">#FF3843C4</Color>

    <LinearGradientBrush x:Key="MenuPopupBrush"
                   EndPoint="0.5,1"
                   StartPoint="0.5,0">
        <GradientStop Color="{DynamicResource ControlLightColor}"
              Offset="0" />
        <GradientStop Color="{DynamicResource ControlMediumColor}"
              Offset="0.5" />
        <GradientStop Color="{DynamicResource ControlLightColor}"
              Offset="1" />
    </LinearGradientBrush>

    <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill"
                   StartPoint="0,0"
                   EndPoint="1,0">
        <LinearGradientBrush.GradientStops>
            <GradientStopCollection>
                <GradientStop Color="#000000FF"
                  Offset="0" />
                <GradientStop Color="#600000FF"
                  Offset="0.4" />
                <GradientStop Color="#600000FF"
                  Offset="0.6" />
                <GradientStop Color="#000000FF"
                  Offset="1" />
            </GradientStopCollection>
        </LinearGradientBrush.GradientStops>
    </LinearGradientBrush>

    <ControlTemplate x:Key="ExpanderToggleButton"
               TargetType="{x:Type ToggleButton}">
        <Border x:Name="Border"
        CornerRadius="2,0,0,0"
        BorderThickness="0,0,1,0">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1"
                         StartPoint="0.5,0">
                    <GradientStop Color="{DynamicResource ControlLightColor}" />
                    <GradientStop Color="{DynamicResource ControlMediumColor}"
                    Offset="1" />
                </LinearGradientBrush>
            </Border.Background>
            <Border.BorderBrush>
                <LinearGradientBrush StartPoint="0,0"
                         EndPoint="0,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStopCollection>
                            <GradientStop Color="{DynamicResource BorderLightColor}"
                        Offset="0.0" />
                            <GradientStop Color="{DynamicResource BorderDarkColor}"
                        Offset="1.0" />
                        </GradientStopCollection>
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>

            </Border.BorderBrush>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Normal" />
                    <VisualState x:Name="MouseOver">
                        <Storyboard>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                        Storyboard.TargetProperty="(Panel.Background).
              (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                <EasingColorKeyFrame KeyTime="0"
                                 Value="{StaticResource ControlMouseOverColor}" />
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Pressed">
                        <Storyboard>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                        Storyboard.TargetProperty="(Panel.Background).
              (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                <EasingColorKeyFrame KeyTime="0"
                                 Value="{StaticResource ControlPressedColor}" />
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Disabled">
                        <Storyboard>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                        Storyboard.TargetProperty="(Panel.Background).
              (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                <EasingColorKeyFrame KeyTime="0"
                                 Value="{StaticResource DisabledControlDarkColor}" />
                            </ColorAnimationUsingKeyFrames>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                        Storyboard.TargetProperty="(Border.BorderBrush).
              (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                <EasingColorKeyFrame KeyTime="0"
                                 Value="{StaticResource DisabledBorderLightColor}" />
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                </VisualStateGroup>
                <VisualStateGroup x:Name="CheckStates">
                    <VisualState x:Name="Checked">
                        <Storyboard>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                         Storyboard.TargetName="CollapsedArrow">
                                <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="{x:Static Visibility.Hidden}" />
                            </ObjectAnimationUsingKeyFrames>
                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)"
                                         Storyboard.TargetName="ExpandededArrow">
                                <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="{x:Static Visibility.Visible}" />
                            </ObjectAnimationUsingKeyFrames>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Unchecked" />
                    <VisualState x:Name="Indeterminate" />
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Grid>
                <Path x:Name="CollapsedArrow"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Data="M 0 0 L 4 4 L 8 0 Z">
                    <Path.Fill>
                        <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                    </Path.Fill>
                </Path>
                <Path x:Name="ExpandededArrow"
          HorizontalAlignment="Center"
          VerticalAlignment="Center"
          Visibility="Collapsed"
          Data="M 0 4 L 4 0 L 8 4 Z">
                    <Path.Fill>
                        <SolidColorBrush Color="{DynamicResource GlyphColor}" />
                    </Path.Fill>
                </Path>
            </Grid>
        </Border>
    </ControlTemplate>

    <Style TargetType="{x:Type Expander}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Expander}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition x:Name="ContentRow" >
                                <RowDefinition.Resources>
                                    <local:MultiplyConverter x:Key="multiplyConverter"/>
                                </RowDefinition.Resources>
                                <RowDefinition.Tag>
                                    <sys:Double>0.0</sys:Double>
                                </RowDefinition.Tag>
                                <RowDefinition.Height>
                                    <MultiBinding Converter="{StaticResource multiplyConverter}">
                                        <Binding Path="DesiredSize.Height" ElementName="Content" />
                                        <Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
                                    </MultiBinding>
                                </RowDefinition.Height>
                            </RowDefinition>
                        </Grid.RowDefinitions>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal" />
                                <VisualState x:Name="MouseOver" />
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                              Storyboard.TargetProperty="(Panel.Background).
                    (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                            <EasingColorKeyFrame KeyTime="0"
                                       Value="{StaticResource DisabledControlDarkColor}" />
                                        </ColorAnimationUsingKeyFrames>
                                        <ColorAnimationUsingKeyFrames Storyboard.TargetName="Border"
                                              Storyboard.TargetProperty="(Border.BorderBrush).
                    (GradientBrush.GradientStops)[1].(GradientStop.Color)">
                                            <EasingColorKeyFrame KeyTime="0"
                                       Value="{StaticResource DisabledBorderLightColor}" />
                                        </ColorAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Border x:Name="Border" Grid.Row="0" BorderThickness="1" CornerRadius="2,2,0,0">
                            <Border.BorderBrush>
                                <LinearGradientBrush EndPoint="0,1"
                                 StartPoint="0,0">
                                    <GradientStop Color="{DynamicResource BorderLightColor}"
                            Offset="0" />
                                    <GradientStop Color="{DynamicResource BorderDarkColor}"
                            Offset="1" />
                                </LinearGradientBrush>
                            </Border.BorderBrush>
                            <Border.Background>

                                <LinearGradientBrush StartPoint="0,0"
                                 EndPoint="0,1">
                                    <LinearGradientBrush.GradientStops>
                                        <GradientStopCollection>
                                            <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0.0" />
                                            <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1.0" />
                                        </GradientStopCollection>
                                    </LinearGradientBrush.GradientStops>
                                </LinearGradientBrush>

                            </Border.Background>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="20" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>
                                <ToggleButton OverridesDefaultStyle="True"
                          Template="{StaticResource ExpanderToggleButton}"
                          IsChecked="{Binding IsExpanded, Mode=TwoWay, 
                RelativeSource={RelativeSource TemplatedParent}}">
                                    <ToggleButton.Background>
                                        <LinearGradientBrush EndPoint="0.5,1"
                                     StartPoint="0.5,0">
                                            <GradientStop Color="{DynamicResource ControlLightColor}"
                                Offset="0" />
                                            <GradientStop Color="{DynamicResource ControlMediumColor}"
                                Offset="1" />
                                        </LinearGradientBrush>
                                    </ToggleButton.Background>
                                </ToggleButton>
                                <ContentPresenter Grid.Column="1" Margin="4" ContentSource="Header" RecognizesAccessKey="True" />
                            </Grid>
                        </Border>
                        <Border x:Name="Content" Grid.Row="1"  BorderThickness="1,0,1,1" CornerRadius="0,0,2,2">
                            <Border.BorderBrush>
                                <SolidColorBrush Color="{DynamicResource BorderMediumColor}" />
                            </Border.BorderBrush>
                            <Border.Background>
                                <SolidColorBrush Color="{DynamicResource ContentAreaColorDark}" />
                            </Border.Background>
                            <ContentPresenter Margin="4" />
                        </Border>

                    </Grid>
                    <ControlTemplate.Triggers> 
                        <Trigger Property="IsExpanded" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ContentRow" Storyboard.TargetProperty="Tag" From="0" To="1" Duration="0:0:1"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard>
                                        <DoubleAnimation Storyboard.TargetName="ContentRow" Storyboard.TargetProperty="Tag" From="1" To="0" Duration="0:0:1"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </Trigger.ExitActions>
                        </Trigger>

                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
<StackPanel>  
    <Expander  Header="Canny" >
        <StackPanel>
            <Button Content="A" />
            <Button Content="B" />
            <Button Content="C" />
        </StackPanel>
    </Expander>

    <Expander Header="Sobel" >
        <StackPanel>
            <Button Content="A" />
            <Button Content="B" />
            <Button Content="C" />
        </StackPanel>
    </Expander>
    <Expander Header="LoG" >
        <StackPanel>
            <Button Content="A" />
            <Button Content="B" />
            <Button Content="C" />
        </StackPanel>
    </Expander>

</StackPanel>


</Window>

Which requires this converter:

public class MultiplyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return new GridLength((double)values[0] * (double)values[1], GridUnitType.Pixel);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The main point is to set DoubleAnimation on ContentRow.Height in the template. However, you need to use a MultiBinding to bind the To property of this animation.