5
votes

I'm currently working on a custom control for my application that expands and collapses content with a header which you can click to change states. The template for it looks like this at the moment.

<Style TargetType="controls:ExpandControl">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="controls:ExpandControl">
                <Border>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="State">
                            <VisualState x:Name="Visible">
                                <VisualState.Setters>
                                    <Setter Target="Grid.Visibility" Value="Visible" />
                                </VisualState.Setters>
                            </VisualState>

                            <VisualState x:Name="Collapsed">
                                <VisualState.Setters>
                                    <Setter Target="Grid.Visibility" Value="Collapsed" />
                                </VisualState.Setters>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>

                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>

                        <ContentPresenter x:Name="HeaderPresenter" Content="{TemplateBinding Header}" />

                        <Grid x:Name="Grid" Grid.Row="1">
                            <ContentPresenter x:Name="ContentPresenter" Content="{TemplateBinding Content}" />
                        </Grid>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

As you can see from the template, I'm currently using visual states to set the visibility of the content in this control but it's not a great user experience as the content just disappears.

I'd like to be able to manipulate the content somehow that would allow the content to look like it's collapsing and expanding from the header when the Visibility of the control changes.

I've taken a look at animations using Storyboards but I'm completely new to that and if anyone could provide some help on Storyboards and how I can make the scenario work for my control, it would be very much appreciated!

Thanks in advance!

1

1 Answers

6
votes

Storyboarding isn't a brilliant experience in Visual Studio and attempting to write them manually may not be the best idea.

I'd recommend opening your project in Blend which comes as part of your Visual Studio installation. It's a great tool for designing your applications, and in particular, adding Storyboards in a very easy manner and it will automatically generate the Storyboard XAML for you while you get to see the changes in the designer.

As for your animation scenario, I've played around with your XAML template in a page and have come up with something that makes it look like it's collapsing and expanding but it does it without manipulating the Visibility property like this:

<VisualStateGroup x:Name="State">
    <VisualState x:Name="Visible">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Grid">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="Grid">
                <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Grid">
                <DiscreteObjectKeyFrame KeyTime="0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Collapsed</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
                <DiscreteObjectKeyFrame KeyTime="0:0:0.1">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </VisualState>

    <VisualState x:Name="Collapsed">
        <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Grid">
                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="Grid">
                <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="Grid">
                <DiscreteObjectKeyFrame KeyTime="0">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
                <DiscreteObjectKeyFrame KeyTime="0:0:0.2">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Collapsed</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </VisualState>
</VisualStateGroup>

You'll also want to change your content Grid to look like this:

<Grid x:Name="Grid" Grid.Row="1" RenderTransformOrigin="0.5,0">
    <Grid.RenderTransform>
        <CompositeTransform/>
    </Grid.RenderTransform>

    <ContentPresenter x:Name="ContentPresenter" Content="{TemplateBinding Content}" />
</Grid>

I'll explain why you'll have to make the changes to the Grid and what the Storyboards do next.

In order to achieve something similar to what you're looking for, I've chosen the Opacity and Y scale on your Grid to animate.

Because we will be manipulating the Y scale of the control, we added the RenderTransform to the Grid. The reason for using the CompositeTransform is so that you can manipulate most common transforms (scale, rotation, translation etc.).

In the states, we use key frames to manipulate the values across time. This is how you achieve the animation in Storyboards. If you only set one KeyFrame with time of 0, it will appear as an immediate change similar to using the VisualState.Setters mechanism of changing properties.

In the Collapsed state, we are changing the opacity and Y scaling of the Grid from 1 to 0. This gives the animation that shows the content collapsing up into the header. As you can see from the key frames, we're staggering the animations of the two properties so the content fades out before it's finished manipulating the scale.

In the Visible state, we are essentially reversing the Collapsed state by changing the opacity and Y scaling from 0 to 1 over the same amount of time.

Try loading these into your control and having a play with them in Blend. It's a great starting point as I threw this together very quickly.

You can find some more information on Storyboarding using Blend here: https://blogs.msdn.microsoft.com/avtarsohi/2016/02/16/understand-storyboard-concept-in-xaml-using-blend-2015/