为什么在设置动画高度设置后,此属性将直接停止工作

时间:2011-01-05 01:05:41

标签: c# wpf animation dependency-properties

我遇到了杀死我的问题。下面是简单的例子

<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>

在代码背后

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);
}

应用程序看起来像这样

alt text

按_resizeButton后左键立即放大。然后我按_animateButton - 左键慢慢放大。在此之后,我再次按_resizeButton,没有任何事情发生。那是为什么?

我注意到在设置Top属性

时也是如此

3 个答案:

答案 0 :(得分:16)

要了解行为,您需要了解动画在WPF属性系统中的工作原理:通过将属性设置为不同的值,动画不起作用。他们通过为暂时优先于'基础'值的财产提供有效价值来工作。这是一个微妙的区别,但它正是导致你在这里脱离的原因。

当第一次遇到动画系统时,大多数人都会想到它通过反复调用属性的“set”访问器来工作。但它没有 - 如果您在开始动画之前设置属性,则您设置的原始值仍然存在。只是getter将返回动画系统提供的值,而不是返回'local'值。实际上,您甚至可以通过在动画运行时设置属性来更改“本地”值,但在动画停止之前,该局部值将不会显示。

实际上,财产系统做了很多这类事情 - 它不仅仅是动画。这个help topic列出了一个属性价值可能来自的11个不同的地方。动画是第二大优先事项。通过'set'访问器(或通过Xaml中的属性)以通常方式设置的属性是次优先级,但是您可以看到模板,样式和触发器都在没有本地属性值时提供其他来源。

WPF中的动画系统具有“基础”值的概念,这实际上是当前动画值之后可用的次高优先级值。如果你有一个本地值,那将是基本值,但如果你没有,那么基本值将来自该文章中列出的其他来源之一。

所有这一切的结果是,没有一种直接的方式去做你似乎想做的事情。我想你想要的是动画运行完成,以及属性保留其最终动画值,直到你将本地值设置为其他值为止。

如果您在动画完成后告诉动画停止,则有效值将恢复为基础值。 (正如你在评论中所说,它在动画完成后缩小。)如果你告诉动画一旦完成(这是默认行为),那么动画将永远提供一个优先级高于本地值的值,这就是为什么你看到手动调整按钮大小不再起作用的原因。所以这两种选择都没有你想要的。

有两种方法可以解决这个问题。 @responderNS5已发布一个 - 处理完动画,修改局部值以反映动画的最终值,然后停止动画。 (你可以认为这是属性的一种降级 - 它将它从高优先级但瞬态动画提供的属性值转换为稍低优先级但更持久的本地值。)我倾向于修改代码有点:

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;
}

我在这里改变的是代码不再试图猜测动画系统最终的位置 - 这只是读取动画系统设置属性的任何值并使其成为新的本地值。我更喜欢+= 10处理程序中的Completed,这对我来说感觉有点脆弱的重复逻辑。

处理它的另一种方法是在尝试更新属性时删除动画:

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);
}

这对我来说似乎稍微强一些,因为即使动画在单击调整大小按钮时尚未完成,这也会起作用。

答案 1 :(得分:3)

您需要将FillBehavior的{​​{1}}设置为DoubleAnimation而不是Stop。看到这篇文章:

答案 2 :(得分:3)

Rick的答案不会像你指出的那样有效。在您的情况下,您需要将动画属性的动画(按钮的高度)设置为null,以便您可以对此属性进行进一步修改。

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; 
    }

将_animateButton_Click处理程序更改为上面指定的处理程序。另外,添加第二个事件处理程序,以便在动画完成时,我们可以删除对“animation”的引用myDoubleAnimation_Completed中的第二个语句就在那里,因为当移除动画引用时,非动画值会显示回来。