У меня есть два варианта решения этой проблемы. Один навязчив, но дает вам больший контроль над фактическим значением, другой не навязчив, но дает вам только косвенный контроль над значением. Я приведу пример кода с обоими вариантами в конце ответа.
Не навязчивое решение
Подпишитесь на событие CurrentTimeInvalidated вашего объекта DoubleAnimation
. Если вы знаете функцию анимации и ее продолжительность, вы можете приблизительно сказать, когда значение анимации близко к вашему событию. Например, продолжительность анимации составляет 500 мс, а функция анимации линейная. Тогда вы можете сказать, что в 250 мс вы на полпути.
Интрузивное решение
Помните: 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
свойство зависимости от реального объекта, вам также придется обновить замороженный объект. Это не совсем желанная практика, и поэтому я называю это навязчивым. Будьте очень осторожны с этим кодом и тщательно тестируйте его. Он просто показывает возможное направление и не является конечным пунктом назначения.
Надеюсь, это поможет