Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why after animating height setting this property directly stops working

I have problem which is killing me. Below is simple example

<Grid Name="_grid">
    <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="36,33,0,0" Name="button" VerticalAlignment="Top" Width="75" />
    <Button Content="Enlarge through animation" Height="23" Margin="172,33,24,0" Name="_animateButton" VerticalAlignment="Top" Click="_animateButton_Click" />
    <Button Content="Enlarge through simple heigh changing" Height="23"  Margin="172,62,24,0" Name="_resizeButton" VerticalAlignment="Top" Click="_resizeButton_Click" />
</Grid>

And in code behind

private void _resizeButton_Click(object sender, RoutedEventArgs e)
{
    button.Height += 10;
}

private void _animateButton_Click(object sender, RoutedEventArgs e)
{
    Storyboard storyboard = new Storyboard();

    DoubleAnimation animation = new DoubleAnimation(button.Height + 10, new Duration(new TimeSpan(0, 0, 0, 1)));
    storyboard.Children.Add(animation);

    Storyboard.SetTargetName(animation, button.Name);
    Storyboard.SetTargetProperty(animation, new PropertyPath(HeightProperty));

    storyboard.Begin(_grid);
}

Application looks like this

alt text

After pressing _resizeButton left button enlarge immediately. Then I'm pressing _animateButton - left button is enlarging slowly. After this I'm pressing _resizeButton again and nothing is happening. Why is that?

I have noticed that the same thing is when animating Top property

like image 233
bizon Avatar asked Jan 05 '11 01:01

bizon


3 Answers

To understand the behaviour, you need to be aware of a feature of how animation works in WPF's property system: animations do not work by setting the property to different values over time. They work by supplying an effective value for the property that temporarily takes precedence over the 'base' value. This is a subtle distinction, but it's what's causing you to come unstuck here.

When first encountering the animation system, most people imagine that it works by repeatedly calling the 'set' accessor for the property. But it doesn't - if you set the property that way before starting the animation remains, the original value you set remains in place. It's just that the getter will return the value supplied by the animation system in preference to returning the 'local' value. In fact, you can even change the 'local' value by setting the property while the animation is running, but that local value will not become visible until the animation stops.

Actually, the property system does a lot of this sort of thing - it's not just animations. This help topic lists 11 different places that a property's value might come from. Animations are the 2nd highest priority. Properties set in the usual fashion via the 'set' accessor (or via attributes in Xaml) are the next highest priority, but you can see that templates, styles, and triggers all provide other sources for when there's no local property value.

The animation system in WPF has a concept of the 'base' value, and that's essentially the next-highest priority value available after the current animated value. If you've got a local value, that'll be the base value, but if you haven't the base value will come from one of the other sources listed in that article.

The upshot of all that is that there isn't a straightforward way to do what you seem to be trying to do. I think what you want is for the animation to run to completion, and for the property to retain its final animation value until such time as you set the local value to something else.

If you tell the animation to stop once it has completed, the effective value reverts back to the base value. (As you say in a comment, it shrinks back after the animation completes.) And if you tell the animation to hold once completed (which is the default behaviour), then the animation will forever provide a value that has higher precedence than local values, which is why you're seeing that resizing the button manually no longer works. So neither option does what you want.

There are two ways to deal with this. @responderNS5 has posted one - handle the completion of the animation, modify the local value to reflect the final value of the animation, and then stop the animation. (You could consider this to be a sort of demotion of the property - it converts it from a high-priority but transient animation-supplied property value into a slightly lower-priority but more persistent local value.) I'd be inclined to modify the code a little:

private void myDoubleAnimation_Completed(object sender, EventArgs e)
{
    // Animation complete, but holding, so Height will currently return
    // return the final animated value.
    double finalHeight = button.Height;

    // Remove the animation.
    button.BeginAnimation(Button.HeightProperty, null);

    // Modify the local value to be the same as the final animated value.
    button.Height = finalHeight;
}

What I've changed here is that the code no longer attempts to guess where the animation system ended up - this just reads whatever value the animation system had set the property to and makes that the new local value. I prefer that to the += 10 in the Completed handler, which feels like slightly fragile duplication of logic to me.

The other way to deal with it is to remove the animation at the point at which you try to update the property:

private void _resizeButton_Click(object sender, RoutedEventArgs e)
{
    double currentHeight = button.Height;
    button.BeginAnimation(Button.HeightProperty, null);
    button.Height = currentHeight + 10;
}

private void _animateButton_Click(object sender, RoutedEventArgs e)
{
    DoubleAnimation animation = new DoubleAnimation
    {
        By = 10,
        Duration = new Duration(TimeSpan.FromSeconds(1))
    };
    button.BeginAnimation(Button.HeightProperty, animation);
}

This seems slightly more robust to me, because this will work even if the animation hadn't finished by the time you click the resize button.

like image 170
Ian Griffiths Avatar answered Nov 01 '22 02:11

Ian Griffiths


You need to set the FillBehavior of the DoubleAnimation to Stop instead of Hold. See this article:

  • Timeline.FillBehavior
like image 33
Rick Sladkey Avatar answered Nov 01 '22 02:11

Rick Sladkey


Rick's answer won't work as you pointed it out. In your case, you need to set the animation of the animated property (height of the button) to null so that you can make further modifications on this property.

http://msdn.microsoft.com/en-us/library/aa970493.aspx

    private void _animateButton_Click(object sender, RoutedEventArgs e) 
    {
        DoubleAnimation animation = new DoubleAnimation();
        animation.By = 10;
        animation.Duration = new Duration(TimeSpan.FromSeconds(1));
        animation.Completed += new EventHandler(myDoubleAnimation_Completed);
        button.BeginAnimation(Button.HeightProperty, animation);
    }

    private void myDoubleAnimation_Completed(object sender, EventArgs e)
    {
        button.BeginAnimation(Button.HeightProperty, null);
        button.Height += 10; 
    }

Change your _animateButton_Click handler to the one indicated above. Also, add the second event handler so that when the animation is completed, we can remove the reference to "animation" The second statement in myDoubleAnimation_Completed is there because when the animation reference is removed, the non-animated value appears back.

like image 3
user1234567 Avatar answered Nov 01 '22 02:11

user1234567