views:

84

answers:

1

I have been trying to get Mike Eshva's code from his answer about Animated Gifs to work, from here:

http://stackoverflow.com/questions/210922/how-do-i-get-an-animated-gif-to-work-in-wpf/1660225#1660225

Restated here (including translated comments):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Diagnostics;

namespace WpfBrowserApplication1 {
    /// <summary> 
    /// Control of the "Images", which supports animated GIF. 
    /// </summary> 
    public class AnimatedImage : Image {
        public AnimatedImage() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedImage), new FrameworkPropertyMetadata(typeof(AnimatedImage)));
        }
    #region Public properties

    /// <summary> 
    /// Gets/sets the number of current frame.
    /// </summary> 
    public int FrameIndex {
        get { return (int)GetValue(FrameIndexProperty); }
        set { SetValue(FrameIndexProperty, value); }
    }

    /// <summary>
    /// Get the BitmapFrame List.
    /// </summary>
    public List<BitmapFrame> Frames { get; private set; }

    /// <summary>
    /// Get or set the repeatBehavior of the animation when source is gif formart.This is a dependency object.
    /// </summary>
    public RepeatBehavior AnimationRepeatBehavior {
        get { return (RepeatBehavior)GetValue(AnimationRepeatBehaviorProperty); }
        set { SetValue(AnimationRepeatBehaviorProperty, value); }
    }

    public new BitmapImage Source {
        get { return (BitmapImage)GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }

    public Uri UriSource {
        get { return (Uri)GetValue(UriSourceProperty); }
        set { SetValue(UriSourceProperty, value); }
    }

    #endregion

    #region Protected interface

    /// <summary> 
    /// Provides derived classes an opportunity to handle changes to the Source property. 
    /// </summary> 
    protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs e) {
        ClearAnimation();
        BitmapImage source;
        if (e.NewValue is Uri) {
            source = new BitmapImage();
            source.BeginInit();
            source.UriSource = e.NewValue as Uri;
            source.CacheOption = BitmapCacheOption.OnLoad;
            source.EndInit();
        } else if (e.NewValue is BitmapImage) {
            source = e.NewValue as BitmapImage;
        } else {
            return;
        }
        BitmapDecoder decoder;
        if (source.StreamSource != null) {
            decoder = BitmapDecoder.Create(source.StreamSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
        } else if (source.UriSource != null) {
            decoder = BitmapDecoder.Create(source.UriSource, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnLoad);
        } else {
            return;
        }
        if (decoder.Frames.Count == 1) {
            base.Source = decoder.Frames[0];
            return;
        }

        this.Frames = decoder.Frames.ToList();

        PrepareAnimation();
    }

    #endregion

    #region Private properties

    private Int32Animation Animation { get; set; }
    private bool IsAnimationWorking { get; set; }

    #endregion

    #region Private methods

    private void ClearAnimation() {
        if (Animation != null) {
            BeginAnimation(FrameIndexProperty, null);
        }

        IsAnimationWorking = false;
        Animation = null;
        this.Frames = null;
    }

    private void PrepareAnimation() {
        Animation =
            new Int32Animation(
                0,
                this.Frames.Count - 1,
                new Duration(
                    new TimeSpan(
                        0,
                        0,
                        0,
                        this.Frames.Count / 10,
                        (int)((this.Frames.Count / 10.0 - this.Frames.Count / 10) * 1000)))) {
                            RepeatBehavior = RepeatBehavior.Forever
                        };

        base.Source = this.Frames[0];
        BeginAnimation(FrameIndexProperty, Animation);
        IsAnimationWorking = true;
    }

    private static void ChangingFrameIndex
        (DependencyObject dp, DependencyPropertyChangedEventArgs e) {
        AnimatedImage animatedImage = dp as AnimatedImage;

        if (animatedImage == null || !animatedImage.IsAnimationWorking) {
            return;
        }

        int frameIndex = (int)e.NewValue;
        ((Image)animatedImage).Source = animatedImage.Frames[frameIndex];
        animatedImage.InvalidateVisual();
    }

    /// <summary> 
    /// Handles changes to the Source property. 
    /// </summary> 
    private static void OnSourceChanged
        (DependencyObject dp, DependencyPropertyChangedEventArgs e) {
        ((AnimatedImage)dp).OnSourceChanged(e);
    }

    #endregion

    #region Dependency Properties

    /// <summary> 
    /// FrameIndex Dependency Property 
    /// </summary> 
    public static readonly DependencyProperty FrameIndexProperty =
        DependencyProperty.Register(
            "FrameIndex",
            typeof(int),
            typeof(AnimatedImage),
            new UIPropertyMetadata(0, ChangingFrameIndex));

    /// <summary> 
    /// Source Dependency Property 
    /// </summary> 
    public new static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register(
            "Source",
            typeof(BitmapImage),
            typeof(AnimatedImage),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.AffectsRender |
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                OnSourceChanged));

    /// <summary>
    /// AnimationRepeatBehavior Dependency Property
    /// </summary>
    public static readonly DependencyProperty AnimationRepeatBehaviorProperty =
        DependencyProperty.Register(
        "AnimationRepeatBehavior",
        typeof(RepeatBehavior),
        typeof(AnimatedImage),
        new PropertyMetadata(null));

    public static readonly DependencyProperty UriSourceProperty =
        DependencyProperty.Register(
        "UriSource",
        typeof(Uri),
        typeof(AnimatedImage),
                new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.AffectsRender |
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                OnSourceChanged));

    #endregion
}

}

Here's the XAML for my page just trying to show one animated GIF:

<Page x:Class="WpfBrowserApplication1.Page1" 
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
      xmlns:my="clr-namespace:WpfBrowserApplication1"
      mc:Ignorable="d" 
      d:DesignHeight="300" d:DesignWidth="300"
      Title="Page1">
    <Grid>
        <my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />
    </Grid>
</Page>

In the design view of Page1, I see the animated gif happily doing what it's supposed to do, no build errors, no design errors, it just works the way it's supposed to. As soon as I start the page to make sure it works in the browser, I get the following error pointed at the line: <my:AnimatedImage x:Name="Wait" Source="arrows.gif" Width="16" Height="16" />

'Set property 'WpfBrowserApplication1.AnimatedImage.Source' threw an exception.' Line number '11' and line position '21'.

I've been trying to get this to work for awhile. What can I do to get the image to animate when the project runs?

+1  A: 

EDIT:

You can download the whole project from my blog (Click on Steve)

This one works fine:

XAML:

<controls:GifImage x:Name="gifImage" Stretch="None" GifSource="/SomeImage.gif" AutoStart="True" />

Note:

If AutoStart is set to false, you need to call either Show() or StartAnimation() manually from your code.

C#:

 public class GifImage : Image
 {
  #region Memmbers

  private GifBitmapDecoder _gifDecoder;
  private Int32Animation _animation;
  private bool _isInitialized;

  #endregion Memmbers

  #region Properties

  private int FrameIndex
  {
   get { return (int)GetValue(FrameIndexProperty); }
   set { SetValue(FrameIndexProperty, value); }
  }

  private static readonly DependencyProperty FrameIndexProperty =
   DependencyProperty.Register("FrameIndex", typeof(int), typeof(GifImage), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(ChangingFrameIndex)));

  private static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
  {
   GifImage image = obj as GifImage;
   image.Source = image._gifDecoder.Frames[(int)ev.NewValue];
  }

  /// <summary>
  /// Defines whether the animation starts on it's own
  /// </summary>
  public bool AutoStart
  {
   get { return (bool)GetValue(AutoStartProperty); }
   set { SetValue(AutoStartProperty, value); }
  }

  public static readonly DependencyProperty AutoStartProperty =
   DependencyProperty.Register("AutoStart", typeof(bool), typeof(GifImage), new UIPropertyMetadata(false, AutoStartPropertyChanged));

  private static void AutoStartPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  {
   if ((bool)e.NewValue)
    (sender as GifImage).StartAnimation();
  }

  public string GifSource
  {
   get { return (string)GetValue(GifSourceProperty); }
   set { SetValue(GifSourceProperty, value); }
  }

  public static readonly DependencyProperty GifSourceProperty =
   DependencyProperty.Register("GifSource", typeof(string), typeof(GifImage), new UIPropertyMetadata(string.Empty, GifSourcePropertyChanged));

  private static void GifSourcePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
  {
   // CARLO 20100622: Reinitialize animation everytime image is changed
   (sender as GifImage).Initialize();
  }

  #endregion Properties

  #region Private Instance Methods

  private void Initialize()
  {
   _gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
   _animation = new Int32Animation(0, _gifDecoder.Frames.Count - 1, new Duration(new TimeSpan(0, 0, 0, _gifDecoder.Frames.Count / 10, (int)((_gifDecoder.Frames.Count / 10.0 - _gifDecoder.Frames.Count / 10) * 1000))));
   _animation.RepeatBehavior = RepeatBehavior.Forever;
   this.Source = _gifDecoder.Frames[0];

   _isInitialized = true;
  }

  #endregion Private Instance Methods

  #region Public Instance Methods

  /// <summary>
  /// Shows and starts the gif animation
  /// </summary>
  public void Show()
  {
   this.Visibility = Visibility.Visible;
   this.StartAnimation();
  }

  /// <summary>
  /// Hides and stops the gif animation
  /// </summary>
  public void Hide()
  {
   this.Visibility = Visibility.Collapsed;
   this.StopAnimation();
  }

  /// <summary>
  /// Starts the animation
  /// </summary>
  public void StartAnimation()
  {
   if (!_isInitialized)
    this.Initialize();

   BeginAnimation(FrameIndexProperty, _animation);
  }

  /// <summary>
  /// Stops the animation
  /// </summary>
  public void StopAnimation()
  {
   BeginAnimation(FrameIndexProperty, null);
  }

  #endregion Public Instance Methods
 }
Carlo
Tried this. Received a NotSupportedException: "The URI prefix is not recognized."
Corey Ogburn
My bad, you need to prefix the image path with a "/" like so: GifSource="/SomeImage.gif". I'll fix it in the answer.
Carlo
Switched -1 to 1 knowing that, and it does work now.
Corey Ogburn
Cool, sorry for the inconvenience and glad I could help =)
Carlo