using System;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Media.Animation;
namespace WpfAnimatedGif
{
///
/// Provides a way to pause, resume or seek a GIF animation.
///
public class ImageAnimationController : IDisposable
{
private static readonly DependencyPropertyDescriptor _sourceDescriptor;
static ImageAnimationController()
{
_sourceDescriptor = DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof (Image));
}
private readonly Image _image;
private readonly ObjectAnimationUsingKeyFrames _animation;
private readonly AnimationClock _clock;
private readonly ClockController _clockController;
internal ImageAnimationController(Image image, ObjectAnimationUsingKeyFrames animation, bool autoStart)
{
_image = image;
_animation = animation;
_animation.Completed += AnimationCompleted;
_clock = _animation.CreateClock();
_clockController = _clock.Controller;
_sourceDescriptor.AddValueChanged(image, ImageSourceChanged);
// ReSharper disable once PossibleNullReferenceException
_clockController.Pause();
_image.ApplyAnimationClock(Image.SourceProperty, _clock);
if (autoStart)
_clockController.Resume();
}
void AnimationCompleted(object sender, EventArgs e)
{
_image.RaiseEvent(new System.Windows.RoutedEventArgs(ImageBehavior.AnimationCompletedEvent, _image));
}
private void ImageSourceChanged(object sender, EventArgs e)
{
OnCurrentFrameChanged();
}
///
/// Returns the number of frames in the image.
///
public int FrameCount
{
get { return _animation.KeyFrames.Count; }
}
///
/// Returns the duration of the animation.
///
public TimeSpan Duration
{
get
{
return _animation.Duration.HasTimeSpan
? _animation.Duration.TimeSpan
: TimeSpan.Zero;
}
}
///
/// Returns a value that indicates whether the animation is paused.
///
public bool IsPaused
{
get { return _clock.IsPaused; }
}
///
/// Returns a value that indicates whether the animation is complete.
///
public bool IsComplete
{
get { return _clock.CurrentState == ClockState.Filling; }
}
///
/// Seeks the animation to the specified frame index.
///
/// The index of the frame to seek to
public void GotoFrame(int index)
{
var frame = _animation.KeyFrames[index];
_clockController.Seek(frame.KeyTime.TimeSpan, TimeSeekOrigin.BeginTime);
}
///
/// Returns the current frame index.
///
public int CurrentFrame
{
get
{
var time = _clock.CurrentTime;
var frameAndIndex =
_animation.KeyFrames
.Cast()
.Select((f, i) => new { Time = f.KeyTime.TimeSpan, Index = i })
.FirstOrDefault(fi => fi.Time >= time);
if (frameAndIndex != null)
return frameAndIndex.Index;
return -1;
}
}
///
/// Pauses the animation.
///
public void Pause()
{
_clockController.Pause();
}
///
/// Starts or resumes the animation. If the animation is complete, it restarts from the beginning.
///
public void Play()
{
_clockController.Resume();
}
///
/// Raised when the current frame changes.
///
public event EventHandler CurrentFrameChanged;
private void OnCurrentFrameChanged()
{
EventHandler handler = CurrentFrameChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
///
/// Finalizes the current object.
///
~ImageAnimationController()
{
Dispose(false);
}
///
/// Disposes the current object.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Disposes the current object
///
/// true to dispose both managed an unmanaged resources, false to dispose only managed resources
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_image.BeginAnimation(Image.SourceProperty, null);
_animation.Completed -= AnimationCompleted;
_sourceDescriptor.RemoveValueChanged(_image, ImageSourceChanged);
_image.Source = null;
}
}
}
}