开发者

Callback function when value equals specified number

I'm quite new in WPF-Animations so apologize if it's too easy, but I can't find any answer (nor my question). So: I have very simple animation - some ca开发者_StackOverflow中文版nvas is rotating from angle -45 degrees to 45 degrees. All animation is made in XAML (got some issues with code-behind animation). I would like to bind function when value equals 0 (e.g. make some noise then). How can I approach this? Thank you for all hints.


I have two options to solve this problem. One is intrusive but gives you more control over the actual value, another is not intrusive but gives you only indirect control over the value. I'll give the sample code with both options at the end of the answer.

Non intrusive solution

Subscribe to the CurrentTimeInvalidated event on your DoubleAnimation object. If you know the animation function and its duration you can approximately say when the animation value is close to your event. For say, animation duration is 500 ms, and the animation function is linear. Then you can say, that at 250ms you are halfway through.

Intrusive solution

Remember: DoubleAnimation (like any other animation) is just a class and you are welcome to inherit it and override any virtual member. In case of DoubleAnimation of particular interest is GetCurrentValueCore() method. And of course you can define any events or dependency properties on this new class. Now you see where it's all going. Inherit DoubleAnimation, override GetCurrentValueCore(), define ValueChanged event, and fire it on every call to GetCurrentValueCore().

Code example

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

There is one caveat though: animation pipeline works with Freezable objects, so you'll have to override CreateInstanceCore() method and return proper instance. Furthermore, if you change Callback dependency property on the real object you'll have to also update the frozen one. It is not quite welcome practice and that's why I call it intrusive. Be very careful with this code and test it throughly. It just shows a possible direction and is not the final destination.

Hope this helps

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜