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