当值等于指定的数字时的回调函数

时间:2011-01-14 15:14:06

标签: wpf

我在WPF-Animations中很新,所以如果太容易就道歉,但我找不到任何答案(也不是我的问题)。所以: 我有非常简单的动画 - 一些画布从-45度到45度旋转。所有动画都是在XAML中制作的(在代码隐藏动画方面遇到了一些问题)。我想在值等于0时绑定函数(例如,然后制造一些噪声)。我怎么处理这个? 谢谢你的所有提示。

1 个答案:

答案 0 :(得分:2)

我有两个选择来解决这个问题。一个是侵入性的,但是你可以更好地控制实际值,另一个不是侵入性的,但只能间接控制价值。我将在答案的最后给出示例代码和两个选项。

非侵入性解决方案

订阅DoubleAnimation对象上的CurrentTimeInvalidated活动。如果你知道动画功能及其持续时间,你可以大致说出动画值何时接近你的事件。例如,动画持续时间为500毫秒,动画功能是线性的。然后你可以说,在250ms你就到了一半。

侵入式解决方案

请记住:DoubleAnimation(与任何其他动画一样)只是一个类,欢迎您继承它并覆盖任何虚拟成员。如果DoubleAnimation特别感兴趣的是GetCurrentValueCore()方法。当然,您可以在此新类上定义任何事件或依赖项属性。现在你看到它的全部发展方向。继承DoubleAnimation,覆盖GetCurrentValueCore(),定义ValueChanged事件,并在每次调用GetCurrentValueCore()时触发它。

代码示例

<强> MainWindow.xaml

<Window x:Class="WpfPlayground.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525"
        xmlns:l="clr-namespace:WpfPlayground">
  <Grid>
    <Grid.Triggers>
      <EventTrigger RoutedEvent="Loaded">
        <BeginStoryboard>
          <Storyboard Duration="00:00:00.500" Storyboard.TargetName="rectangle" RepeatBehavior="Forever">
            <l:DoubleAnimationWithCallback From="0" 
                                           To="180" Duration="00:00:00.500"
                                           Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(RotateTransform.Angle)"
                                           Callback="{Binding AnimationCallback, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:MainWindow}}}" 
                                           CurrentTimeInvalidated="OnCurrentTimeInvalidated" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Grid.Triggers>
    <!--We animate this rectangle-->
    <Rectangle x:Name="rectangle" Width="50" Height="50" Fill="Green">
      <Rectangle.LayoutTransform>
        <RotateTransform  />
      </Rectangle.LayoutTransform>
    </Rectangle>
    <!--Debug information-->
    <TextBlock x:Name="tbTime" HorizontalAlignment="Center" VerticalAlignment="Top"/>
    <TextBlock x:Name="tbAngle" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
  </Grid>
</Window>

<强> MainWindow.xaml.cs

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Globalization;

namespace WpfPlayground
{
  public partial class MainWindow : Window
  {
    public Func<double, double> AnimationCallback { get { return AnimationCallbackImpl; } }

    public MainWindow()
    {
      InitializeComponent();
    }

    private double AnimationCallbackImpl(double value)
    {
      tbAngle.Text = value.ToString(CultureInfo.CurrentCulture);
      return value;
    }

    private void OnCurrentTimeInvalidated(object sender, EventArgs e)
    {
      tbTime.Text = ((AnimationClock)sender).CurrentTime.ToString();
    }
  }
}

<强> DoubleAnimationWithCallback.cs

using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace WpfPlayground
{
  public class DoubleAnimationWithCallback : DoubleAnimation
  {
    // Cache Callback DP, to avoid performance hit.
    private Func<double, double> _callback;

    // reference to frozen instance. See comments below for explanation.
    private DoubleAnimationWithCallback _coreInstance;

    public Func<double, double> Callback
    {
      get { return (Func<double, double>)GetValue(CallbackProperty); }
      set { SetValue(CallbackProperty, value); }
    }

    public static readonly DependencyProperty CallbackProperty =
        DependencyProperty.Register("Callback", typeof(Func<double, double>), typeof(DoubleAnimationWithCallback), new PropertyMetadata(null, OnCallbackChanged));

    private static void OnCallbackChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
      var dawc = o as DoubleAnimationWithCallback;
      if (dawc != null)
      {
        dawc.UpdateCallback(e.NewValue as Func<double, double>);
      }
    }

    private void UpdateCallback(Func<double, double> callback)
    {
      _callback = callback;
      if (_coreInstance != null)
      {
        _coreInstance._callback = _callback;
      }
    }

    protected override Freezable CreateInstanceCore()
    {
      if (_coreInstance == null)
      {
        // When callback changes we update corresponding callback on
        // the frozen object too.
        _coreInstance = new DoubleAnimationWithCallback()
        {
          Callback = Callback
        };
      }
      return _coreInstance;
    }

    protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock)
    {
      var value = base.GetCurrentValueCore(defaultOriginValue, defaultDestinationValue, animationClock);
      if (_callback != null)
      {
        return _callback(value);
      }
      return value;
    }
  }
}

但有一点需要注意:动画管道与Freezable个对象一起使用,因此您必须覆盖CreateInstanceCore()方法并返回正确的实例。此外,如果您在真实对象上更改Callback依赖项属性,则还必须更新冻结的属性。这不是很受欢迎的做法,这就是我称之为侵入性的原因。小心这段代码并彻底测试。它只显示了一个可能的方向,而不是最终目的地。

希望这有帮助