¿Cómo consigo un gif animado para trabajar en WPF?


218

¿Qué tipo de control debería utilizar - Image, MediaElement, etc.?


44
Aquí hay un resumen reciente de las siguientes soluciones. Los implementé usando VS2015. La clase GifImage presentada por Dario funcionó muy bien, pero algunos de mis gifs fueron alterados. El enfoque de MediaElement de Pradip Daunde y nicael parece funcionar en el área de vista previa, pero ninguno de mis gifs se muestra durante el tiempo de ejecución. La solución WpfAnimatedGif de IgorVaschuk y SaiyanGirl funcionó muy bien sin problemas, pero requirió instalar una biblioteca de terceros (obviamente). No probé el resto.
Heath Carroll

Respuestas:


214

No pude obtener la respuesta más popular a esta pregunta (arriba por Dario) para que funcione correctamente. El resultado fue una animación extraña y entrecortada con artefactos extraños. La mejor solución que he encontrado hasta ahora: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Puedes instalarlo con NuGet

PM> Install-Package WpfAnimatedGif

y para usarlo, en un nuevo espacio de nombres a la ventana donde desea agregar la imagen gif y usarla como se muestra a continuación

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:gif="http://wpfanimatedgif.codeplex.com" <!-- THIS NAMESPACE -->
    Title="MainWindow" Height="350" Width="525">

<Grid>
    <!-- EXAMPLE USAGE BELOW -->
    <Image gif:ImageBehavior.AnimatedSource="Images/animated.gif" />

El paquete es realmente ordenado, puede establecer algunos atributos como a continuación

<Image gif:ImageBehavior.RepeatBehavior="3x"
       gif:ImageBehavior.AnimatedSource="Images/animated.gif" />

y también puedes usarlo en tu código:

var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(fileName);
image.EndInit();
ImageBehavior.SetAnimatedSource(img, image);

EDITAR: soporte Silverlight

Según el comentario de josh2112 si desea agregar soporte GIF animado a su proyecto Silverlight, use github.com/XamlAnimatedGif/XamlAnimatedGif


13
Esto funcionó muy bien y tardó menos de 60 segundos en implementarse. ¡Gracias!
Ryan Sorensen

3
Respuesta mucho mejor que cualquiera de los populares IMO, especialmente porque no depende de ti usando C #
Jamie E

8
Esto es mucho mejor que la respuesta aceptada: utiliza los metadatos del gif, no es entrecortado, es un paquete NuGet, es independiente del lenguaje. Deseo que stackoverflow permita un voto de no confianza en la respuesta aceptada.
John Gietzen

66
Anuncio de servicio público: el autor de WpfAnimatedGif ha "reiniciado" su proyecto como XamlAnimatedGif, y es compatible con WPF, Windows Store (Win8), Windows 10 y Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Que hay imgaqui
amit jha

104

Publico una solución que extiende el control de la imagen y usa el Decodificador de GIF. El decodificador de gif tiene una propiedad de marcos. Animo la FrameIndexpropiedad. El evento ChangingFrameIndexcambia la propiedad de origen al marco correspondiente al FrameIndex(que está en el decodificador). Supongo que el gif tiene 10 cuadros por segundo.

class GifImage : Image
{
    private bool _isInitialized;
    private GifBitmapDecoder _gifDecoder;
    private Int32Animation _animation;

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

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

    static GifImage()
    {
        VisibilityProperty.OverrideMetadata(typeof (GifImage),
            new FrameworkPropertyMetadata(VisibilityPropertyChanged));
    }

    private static void VisibilityPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        if ((Visibility)e.NewValue == Visibility.Visible)
        {
            ((GifImage)sender).StartAnimation();
        }
        else
        {
            ((GifImage)sender).StopAnimation();
        }
    }

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

    static void ChangingFrameIndex(DependencyObject obj, DependencyPropertyChangedEventArgs ev)
    {
        var gifImage = obj as GifImage;
        gifImage.Source = gifImage._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)
    {
        (sender as GifImage).Initialize();
    }

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

Ejemplo de uso (XAML):

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

1
Este funciona, y mejor para aplicaciones XBAP, porque no necesita referencias adicionales.
Max Galkin

1
Eso es genial. Al poner su código de constructor en el evento "Inicializado" e introducir una propiedad Uri, este control también se puede colocar en un archivo XAML.
flq 9/10

1
+1, buena! Sin embargo, no tiene en cuenta la duración real del cuadro de la imagen ... Si puede encontrar una manera de leer esa información, puede cambiar el código para usar unInt32AnimationUsingKeyFrames
Thomas Levesque

77
En realidad, la velocidad de fotogramas es constante para GIF, por lo que no necesita fotogramas clave después de todo ... Puede leer la velocidad de fotogramas con gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(devuelve un valor corto que es la duración del fotograma en cientos de segundos)
Thomas Levesque

3
@vidstige, sí, no recuerdo por qué había hecho este comentario en ese momento (hace casi 2 años). Soy consciente de que el retraso puede ser diferente para cada fotograma, y ​​mi biblioteca de GIF animado de WPF lo tiene en cuenta correctamente.
Thomas Levesque

38

Yo también hice una búsqueda y encontré varias soluciones diferentes en solo un hilo en los viejos foros de MSDN. (el enlace ya no funcionaba, así que lo eliminé)

La forma más simple de ejecutar parece ser usar un WinForms PictureBox control , y así fue (cambió algunas cosas del hilo, la mayoría de las mismas).

Agregue una referencia a System.Windows.Forms, WindowsFormsIntegrationy System.Drawinga su proyecto primero.

<Window x:Class="GifExample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
    xmlns:winForms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
    Loaded="Window_Loaded" >
    <Grid>
        <wfi:WindowsFormsHost>
            <winForms:PictureBox x:Name="pictureBoxLoading">
            </winForms:PictureBox>
        </wfi:WindowsFormsHost>
    </Grid>
</Window >

Luego, en el Window_Loadedcontrolador, establecería la pictureBoxLoading.ImageLocationpropiedad en la ruta del archivo de imagen que desea mostrar.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    pictureBoxLoading.ImageLocation = "../Images/mygif.gif";
}

El MediaElementcontrol se mencionó en ese hilo, pero también se menciona que es un control bastante pesado, por lo que había una serie de alternativas, que incluían al menos 2 controles caseros basados ​​en el Imagecontrol, por lo que este es el más simple.


¿Puedes poner esta ventana principal con AllowTransparency = "True" cuando usas WindowsFormsHost?
Junior Mayhé

@ Junior: Sí, puedes configurar AllowTransparency="True". Si eso producirá o no los resultados que tiene en mente es otro asunto. No lo he intentado yo mismo, pero apuesto a que WindowsFormsHostno se volverá transparente en absoluto. El resto del Windowpoder. Simplemente tendrás que probarlo, creo.
Joel B Fant

Tuve problemas con pictureBoxLoading.Image debido a la API winform. Publiqué el código a continuación que resolvió mi problema. Gracias por tu solución, Joel!
sondlerd

Parece que tu gusto está muerto. ¿Fue este hilo ?
wip

2
Al agregar la referencia de integración, su nombre en mi interfaz de usuario es WindowsFormsIntegration, sin punto: i.imgur.com/efMiC23.png
yu yang Jian

36

¿Qué tal esta pequeña aplicación? Código detrás:

public MainWindow()
{
  InitializeComponent();
  Files = Directory.GetFiles(@"I:\images");
  this.DataContext= this;
}
public string[] Files
{get;set;}

XAML:

<Window x:Class="PicViewer.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">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="175" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <ListBox x:Name="lst" ItemsSource="{Binding Path=Files}"/>
        <MediaElement Grid.Column="1" LoadedBehavior="Play" Source="{Binding ElementName=lst, Path=SelectedItem}" Stretch="None"/>
    </Grid>
</Window>

1
Agradable ! Código corto, haciendo bien el trabajo. No puedo creer que no tenga más votos a favor.
wip

2
La mejor respuesta ... ¡Debería estar en la cima! Pude hacerlo funcionar sin ningún código detrás, solo esto <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >: MyGifFile es solo el nombre del archivo (y la ruta) de mi gif animado.
Anthony Nichols

Por Dios, ¿por qué molestarse en unírsele ListBox, o ligarse en absoluto? Lo probé sin vinculación, simplemente puse la ruta del archivo en la Fuente y aparece, pero no se anima. Si uso el enlace, incluso con el ListBox, no aparece en absoluto, para mí, me dará una excepción de que mi ruta de archivo es incorrecta, a pesar de que es la misma que uso cuando aparece.
vapcguy

La actualización lleva mucho tiempo y debe actualizarse cada vez que aparece.
Yola

15

Es muy simple si usas <MediaElement>:

<MediaElement  Height="113" HorizontalAlignment="Left" Margin="12,12,0,0" 
Name="mediaElement1" VerticalAlignment="Top" Width="198" Source="C:\Users\abc.gif"
LoadedBehavior="Play" Stretch="Fill" SpeedRatio="1" IsMuted="False" />

Sólo en caso de que su archivo está empaquetado en su aplicación se puede utilizar para la DataBinding Fuente y encontrar el camino de código: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Asegúrese de configurar el archivo en Build = Content y copie en el directorio de salida.
The Muffin Man el

Utilicé este enfoque porque el paquete WpfAnimatedGif NuGet no funcionó bien para mí, parecía fallar cuando estaba bajo una gran carga de CPU. Configuré el gif en Build = Resource y configuré la fuente usando una ruta relativa desde la carpeta en la que estaba la ventana, por ejemplo, Source = "../../ Images / Rotating -.e.if". Me funcionó bien y no necesito archivos DLL de terceros.
Richard Moore

Esta es la solución más simple con diferencia. Pero el problema es que una vez que se escanean todos los cuadros del gif animado, la animación se detiene. Y no hay forma de volver a animar el gif desde el fotograma 0. No hay forma de reiniciar la animación o el bucle para siempre. Al menos, no he encontrado una manera de usar <MediaElement />.
BoiseBaked

Además, <MediaElement /> es increíblemente lento y está lleno de problemas de hilo entre sus métodos. Grrr ...
BoiseBaked

10

Aquí está mi versión de control de imagen animada. Puede usar la propiedad estándar Fuente para especificar la fuente de la imagen. Lo mejoré aún más. Soy ruso, el proyecto es ruso, así que los comentarios también están en ruso. Pero de todos modos deberías poder entender todo sin comentarios. :)

/// <summary>
/// Control the "Images", which supports animated GIF.
/// </summary>
public class AnimatedImage : Image
{
    #region Public properties

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

    /// <summary>
    /// Gets / sets the image that will be drawn.
    /// </summary>
    public new ImageSource Source
    {
        get { return (ImageSource) GetValue(SourceProperty); }
        set { SetValue(SourceProperty, value); }
    }

    #endregion

    #region Protected interface

    /// <summary>
    /// Provides derived classes an opportunity to handle changes to the Source property.
    /// </summary>
    protected virtual void OnSourceChanged(DependencyPropertyChangedEventArgs aEventArgs)
    {
        ClearAnimation();

        BitmapImage lBitmapImage = aEventArgs.NewValue as BitmapImage;

        if (lBitmapImage == null)
        {
            ImageSource lImageSource = aEventArgs.NewValue as ImageSource;
            base.Source = lImageSource;
            return;
        }

        if (!IsAnimatedGifImage(lBitmapImage))
        {
            base.Source = lBitmapImage;
            return;
        }

        PrepareAnimation(lBitmapImage);
    }

    #endregion

    #region Private properties

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

    #endregion

    #region Private methods

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

        IsAnimationWorking = false;
        Animation = null;
        Decoder = null;
    }

    private void PrepareAnimation(BitmapImage aBitmapImage)
    {
        Debug.Assert(aBitmapImage != null);

        if (aBitmapImage.UriSource != null)
        {
            Decoder = new GifBitmapDecoder(
                aBitmapImage.UriSource,
                BitmapCreateOptions.PreservePixelFormat,
                BitmapCacheOption.Default);
        }
        else
        {
            aBitmapImage.StreamSource.Position = 0;
            Decoder = new GifBitmapDecoder(
                aBitmapImage.StreamSource,
                BitmapCreateOptions.PreservePixelFormat,
                BitmapCacheOption.Default);
        }

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

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

    private bool IsAnimatedGifImage(BitmapImage aBitmapImage)
    {
        Debug.Assert(aBitmapImage != null);

        bool lResult = false;
        if (aBitmapImage.UriSource != null)
        {
            BitmapDecoder lBitmapDecoder = BitmapDecoder.Create(
                aBitmapImage.UriSource,
                BitmapCreateOptions.PreservePixelFormat,
                BitmapCacheOption.Default);
            lResult = lBitmapDecoder is GifBitmapDecoder;
        }
        else if (aBitmapImage.StreamSource != null)
        {
            try
            {
                long lStreamPosition = aBitmapImage.StreamSource.Position;
                aBitmapImage.StreamSource.Position = 0;
                GifBitmapDecoder lBitmapDecoder =
                    new GifBitmapDecoder(
                        aBitmapImage.StreamSource,
                        BitmapCreateOptions.PreservePixelFormat,
                        BitmapCacheOption.Default);
                lResult = lBitmapDecoder.Frames.Count > 1;

                aBitmapImage.StreamSource.Position = lStreamPosition;
            }
            catch
            {
                lResult = false;
            }
        }

        return lResult;
    }

    private static void ChangingFrameIndex
        (DependencyObject aObject, DependencyPropertyChangedEventArgs aEventArgs)
    {
        AnimatedImage lAnimatedImage = aObject as AnimatedImage;

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

        int lFrameIndex = (int) aEventArgs.NewValue;
        ((Image) lAnimatedImage).Source = lAnimatedImage.Decoder.Frames[lFrameIndex];
        lAnimatedImage.InvalidateVisual();
    }

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

    #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 (ImageSource),
            typeof (AnimatedImage),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.AffectsRender |
                FrameworkPropertyMetadataOptions.AffectsMeasure,
                OnSourceChanged));

    #endregion
}

15
Este código es parte de uno de mis proyectos. Soy un desarrollador ruso que trabaja en Rusia. Entonces los comentarios también están en ruso. No todos los proyectos en el mundo son proyectos de "inglés americano", Corey.
Mike Eshva

2
intenté usar su código con el siguiente marcado: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" /> pero hasta ahora no está sucediendo nada
Sonic Soul

Si lo cambio a usar un JPEG, muestra la imagen fija. solo que no el gif. bonito código BTW
Sonic Soul

Brillante, necesitaba una solución donde pudiera pero un GIF del Diccionario de recursos -> BitmapImage -> GIF animado. ¡Eso es todo!
mtbennett

9

Yo uso esta biblioteca: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Primero, instale la biblioteca en su proyecto (usando Package Manager Console):

    PM > Install-Package WpfAnimatedGif

Luego, use este fragmento en el archivo XAML:

    <Window x:Class="WpfAnimatedGif.Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:gif="http://wpfanimatedgif.codeplex.com"
        Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Image gif:ImageBehavior.AnimatedSource="Images/animated.gif" />
        ...

Espero ayuda

Fuente: https://github.com/XamlAnimatedGif/WpfAnimatedGif


3
Esta es la misma respuesta (menos detallada) que la de @ IgorVaschuk de junio de 2012, actualmente la solución del segundo lugar en cuanto a votos.
Heath Carroll

5

Básicamente, la misma solución PictureBox anterior, pero esta vez con el código subyacente para usar un recurso integrado en su proyecto:

En XAML:

<WindowsFormsHost x:Name="_loadingHost">
  <Forms:PictureBox x:Name="_loadingPictureBox"/>
</WindowsFormsHost>

En Code-Behind:

public partial class ProgressIcon
{
    public ProgressIcon()
    {
        InitializeComponent();
        var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("My.Namespace.ProgressIcon.gif");
        var image = System.Drawing.Image.FromStream(stream);
        Loaded += (s, e) => _loadingPictureBox.Image = image;
    }
}

Buena adición Realmente lo racionaliza, por lo que puedo decir. (Dicho esto, no he escrito en WPF en más de tres años, ahora.)
CodeMouse92

Realmente no creo que sea una buena idea porque una de las principales razones por las que optas por WPF es por su escala de visualización. Terminarás con un artefacto (la imagen) que no se escala correctamente.
The Muffin Man el

5

Modifiqué el código de Mike Eshva, y lo hice funcionar mejor. Puedes usarlo con 1frame jpg png bmp o mutil-frame gif. Si quieres vincular una uri al control, vincula las propiedades de UriSource o quieres vincular cualquier flujo de memoria que une la propiedad de origen, que es una imagen de mapa de bits.

    /// <summary> 
/// Элемент управления "Изображения", поддерживающий анимированные GIF. 
/// </summary> 
public class AnimatedImage : Image
{
    static AnimatedImage()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(AnimatedImage), new FrameworkPropertyMetadata(typeof(AnimatedImage)));
    }

    #region Public properties

    /// <summary> 
    /// Получает/устанавливает номер текущего кадра. 
    /// </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
}

Este es un control personalizado. Debe crearlo en WPF App Project y eliminar la anulación de plantilla con estilo.


1
Solo tenía que configurar UriSource para empacar: // application: ,,, / Images / loader.gif. Establecer UriSource o Source en un Uri relativo falló en tiempo de ejecución.
Farzan el

Sí, lo he intentado y obtengo una excepción. No funciona con uris relativas.
SuperJMN

3

Tuve este problema, hasta que descubrí que en WPF4, puedes simular tus propias animaciones de imágenes de fotogramas clave. Primero, divida su animación en una serie de imágenes, asígneles un título similar a "Image1.gif", "Image2, gif", etc. Importe esas imágenes en los recursos de su solución. Supongo que los coloca en la ubicación de recursos predeterminada para las imágenes.

Vas a usar el control de imagen. Use el siguiente código XAML. He eliminado lo no esencial.

<Image Name="Image1">
   <Image.Triggers>
      <EventTrigger RoutedEvent="Image.Loaded"
         <EventTrigger.Actions>
            <BeginStoryboard>
               <Storyboard>
                   <ObjectAnimationUsingKeyFrames Duration="0:0:1" Storyboard.TargetProperty="Source" RepeatBehavior="Forever">
                      <DiscreteObjectKeyFrames KeyTime="0:0:0">
                         <DiscreteObjectKeyFrame.Value>
                            <BitmapImage UriSource="Images/Image1.gif"/>
                         </DiscreteObjectKeyFrame.Value>
                      </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.25">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image2.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.5">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image3.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:0.75">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image4.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                     <DiscreteObjectKeyFrames KeyTime="0:0:1">
                        <DiscreteObjectKeyFrame.Value>
                           <BitmapImage UriSource="Images/Image5.gif"/>
                        </DiscreteObjectKeyFrame.Value>
                     </DiscreteObjectKeyFrames>
                  </ObjectAnimationUsingKeyFrames>
               </Storyboard>
            </BeginStoryboard>
         </EventTrigger.Actions>
      </EventTrigger>
   </Image.Triggers>
</Image>

1
Parece que una desventaja de este enfoque es que, de forma predeterminada, la animación continúa incluso después de contraerse, lo que puede causar un impacto en el rendimiento.
Lynn

No es DiscreteObjectKeyFrames, es DiscreteObjectKeyFrame. Singular.
jairhumberto

@jairhumberto Creo que eso puede haber cambiado entre versiones. Esto es bastante antiguo (2011), pero de hecho estaba usando este código exacto en un proyecto.
CodeMouse92

3

Gracias por tu publicación Joel, me ayudó a resolver la ausencia de soporte de WPF para GIF animados. Solo agregué un pequeño código ya que tuve un montón de tiempo configurando la propiedad pictureBoxLoading.Image debido a la API Winforms.

Tuve que configurar la Acción de compilación de mi imagen GIF animada como "Contenido" y Copiar al directorio de salida en "Copiar si es más reciente" o "siempre". Luego, en MainWindow (), llamé a este método. El único problema es que cuando intenté deshacerme de la transmisión, me dio un gráfico de sobre rojo en lugar de mi imagen. Tendré que resolver ese problema. Esto eliminó el dolor de cargar una imagen de mapa de bits y cambiarla a un mapa de bits (que obviamente mató mi animación porque ya no es un gif).

private void SetupProgressIcon()
{
   Uri uri = new Uri("pack://application:,,,/WPFTest;component/Images/animated_progress_apple.gif");
   if (uri != null)
   {
      Stream stream = Application.GetContentStream(uri).Stream;   
      imgProgressBox.Image = new System.Drawing.Bitmap(stream);
   }
}

re: cuando traté de deshacerme de la transmisión Según el MSDN, un mapa de bits que usa una transmisión debe tener la transmisión viva durante la vida del mapa de bits. La solución es congelar o clonar el mapa de bits.
Jesse Chisholm

1
Solo necesitaba decir que se pusiera en .ImageLocationlugar de .Image. Tenía el método equivocado. .ImageLocationfunciona desde la raíz del proyecto de Visual Studio, así que supongamos que tiene una Imagescarpeta, su ruta es entonces imgBox.ImageLocation = "/Images/my.gif";. Si usted tiene una carpeta llamada Viewsdonde se tiene una vista que mostrará la imagen, para volver a Images, usted tendría que usar 2 puntos: imgBox.ImageLocation = "../Images/my.gif";.
vapcguy

1

He intentado todo el camino anterior, pero cada uno tiene su brevedad, y gracias a todos ustedes, trabajo mi propio GifImage:

    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.IO;
    using System.Windows.Threading;

    namespace IEXM.Components
    {
    public class GifImage : Image
    {
            #region gif Source, such as "/IEXM;component/Images/Expression/f020.gif"
            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(null, GifSourcePropertyChanged));

            private static void GifSourcePropertyChanged(DependencyObject sender,
                    DependencyPropertyChangedEventArgs e)
            {
                    (sender as GifImage).Initialize();
            }
            #endregion

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

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

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

            private bool _isInitialized = false;
            private System.Drawing.Bitmap _bitmap;
            private BitmapSource _source;

            [System.Runtime.InteropServices.DllImport("gdi32.dll")]
            public static extern bool DeleteObject(IntPtr hObject);

            private BitmapSource GetSource()
            {
                    if (_bitmap == null)
                    {
                            _bitmap = new System.Drawing.Bitmap(Application.GetResourceStream(
                                     new Uri(GifSource, UriKind.RelativeOrAbsolute)).Stream);
                    }

                    IntPtr handle = IntPtr.Zero;
                    handle = _bitmap.GetHbitmap();

                    BitmapSource bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
                            handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                    DeleteObject(handle);
                    return bs;
            }

            private void Initialize()
            {
            //        Console.WriteLine("Init: " + GifSource);
                    if (GifSource != null)
                            Source = GetSource();
                    _isInitialized = true;
            }

            private void FrameUpdatedCallback()
            {
                    System.Drawing.ImageAnimator.UpdateFrames();

                    if (_source != null)
                    {
                            _source.Freeze();
                    }

               _source = GetSource();

              //  Console.WriteLine("Working: " + GifSource);

                    Source = _source;
                    InvalidateVisual();
            }

            private void OnFrameChanged(object sender, EventArgs e)
            {
                    Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(FrameUpdatedCallback));
            }

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


             //   Console.WriteLine("Start: " + GifSource);

                    System.Drawing.ImageAnimator.Animate(_bitmap, OnFrameChanged);
            }

            /// <summary>
            /// Stops the animation
            /// </summary>
            public void StopAnimation()
            {
                    _isInitialized = false;
                    if (_bitmap != null)
                    {
                            System.Drawing.ImageAnimator.StopAnimate(_bitmap, OnFrameChanged);
                            _bitmap.Dispose();
                            _bitmap = null;
                    }
                    _source = null;
                    Initialize();
                    GC.Collect();
                    GC.WaitForFullGCComplete();

             //   Console.WriteLine("Stop: " + GifSource);
            }

            public void Dispose()
            {
                    _isInitialized = false;
                    if (_bitmap != null)
                    {
                            System.Drawing.ImageAnimator.StopAnimate(_bitmap, OnFrameChanged);
                            _bitmap.Dispose();
                            _bitmap = null;
                    }
                    _source = null;
                    GC.Collect();
                    GC.WaitForFullGCComplete();
               // Console.WriteLine("Dispose: " + GifSource);
            }
    }
}

Uso:

<localComponents:GifImage x:Name="gifImage" IsAutoStart="True" GifSource="{Binding Path=value}" />

Como no causaría pérdida de memoria y animó la línea de tiempo de la imagen GIF, puede probarlo.


Excelente muestra Necesita inicializar actualizado para verificar IsAutoStart, pero de lo contrario, funcionó como un campeón.
Steve Danner

1
Llamar explícitamente a GC.Collect () tiene un impacto horrible en el rendimiento.
Kędrzu

0

Anteriormente, me enfrentaba a un problema similar, necesitaba reproducir el .gifarchivo en su proyecto. Tenía dos opciones:

  • usando PictureBox de WinForms

  • utilizando una biblioteca de terceros, como WPFAnimatedGif de codeplex.com.

La versión con PictureBoxno funcionó para mí, y el proyecto no podía usar bibliotecas externas para ello. Así que lo hice por mí mismo Bitmapcon ayuda ImageAnimator. Porque, el estándar BitmapImageno admite la reproducción de .gifarchivos.

Ejemplo completo:

XAML

<Window x:Class="PlayGifHelp.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" Loaded="MainWindow_Loaded">

    <Grid>
        <Image x:Name="SampleImage" />
    </Grid>
</Window>

Code behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    Bitmap _bitmap;
    BitmapSource _source;

    private BitmapSource GetSource()
    {
        if (_bitmap == null)
        {
            string path = Directory.GetCurrentDirectory();

            // Check the path to the .gif file
            _bitmap = new Bitmap(path + @"\anim.gif");
        }

        IntPtr handle = IntPtr.Zero;
        handle = _bitmap.GetHbitmap();

        return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        _source = GetSource();
        SampleImage.Source = _source;
        ImageAnimator.Animate(_bitmap, OnFrameChanged);
    }

    private void FrameUpdatedCallback()
    {
        ImageAnimator.UpdateFrames();

        if (_source != null)
        {
            _source.Freeze();
        }

        _source = GetSource();

        SampleImage.Source = _source;
        InvalidateVisual();
    }

    private void OnFrameChanged(object sender, EventArgs e)
    {
        Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(FrameUpdatedCallback));
    }
}

Bitmapno es compatible con la directiva URI , por lo que cargo el .gifarchivo desde el directorio actual.


0

Pequeña mejora del GifImage.Initialize()método, que lee el tiempo de trama adecuado de los metadatos GIF.

    private void Initialize()
    {
        _gifDecoder = new GifBitmapDecoder(new Uri("pack://application:,,," + this.GifSource), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);

        int duration=0;
        _animation = new Int32AnimationUsingKeyFrames();
        _animation.KeyFrames.Add(new DiscreteInt32KeyFrame(0, KeyTime.FromTimeSpan(new TimeSpan(0))));
        foreach (BitmapFrame frame in _gifDecoder.Frames)
        {
            BitmapMetadata btmd = (BitmapMetadata)frame.Metadata;
            duration += (ushort)btmd.GetQuery("/grctlext/Delay");
            _animation.KeyFrames.Add(new DiscreteInt32KeyFrame(_gifDecoder.Frames.IndexOf(frame)+1, KeyTime.FromTimeSpan(new TimeSpan(duration*100000))));
        }            
         _animation.RepeatBehavior = RepeatBehavior.Forever;
        this.Source = _gifDecoder.Frames[0];            
        _isInitialized = true;
    }

0

No estoy seguro de si esto se ha resuelto, pero la mejor manera es usar el biblioteca WpfAnimatedGid . Es muy fácil, simple y directo de usar. Solo requiere 2 líneas de código XAML y alrededor de 5 líneas de código C # en el código detrás.

Verá todos los detalles necesarios de cómo se puede usar esto allí. Esto es lo que también usé en lugar de reinventar la rueda


0

Agregando a la respuesta principal que recomienda el uso de WpfAnimatedGif , debe agregar las siguientes líneas al final si está intercambiando una imagen con un Gif para asegurarse de que la animación realmente se ejecute:

ImageBehavior.SetRepeatBehavior(img, new RepeatBehavior(0));
ImageBehavior.SetRepeatBehavior(img, RepeatBehavior.Forever);

Entonces su código se verá así:

var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(fileName);
image.EndInit();
ImageBehavior.SetAnimatedSource(img, image);
ImageBehavior.SetRepeatBehavior(img, new RepeatBehavior(0));
ImageBehavior.SetRepeatBehavior(img, RepeatBehavior.Forever);

0

Revisa mi código, espero que esto te haya ayudado :)

         public async Task GIF_Animation_Pro(string FileName,int speed,bool _Repeat)
                    {
    int ab=0;
                        var gif = GifBitmapDecoder.Create(new Uri(FileName), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
                        var getFrames = gif.Frames;
                        BitmapFrame[] frames = getFrames.ToArray();
                        await Task.Run(() =>
                        {


                            while (ab < getFrames.Count())
                            {
                                Thread.Sleep(speed);
try
{
                                Dispatcher.Invoke(() =>
                                {
                                    gifImage.Source = frames[ab];
                                });
                                if (ab == getFrames.Count - 1&&_Repeat)
                                {
                                    ab = 0;

                                }
                                ab++;
            }
 catch
{
}

                            }
                        });
                    }

o

     public async Task GIF_Animation_Pro(Stream stream, int speed,bool _Repeat)
            {
 int ab = 0;   
                var gif = GifBitmapDecoder.Create(stream , BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
                var getFrames = gif.Frames;
                BitmapFrame[] frames = getFrames.ToArray();
                await Task.Run(() =>
                {


                    while (ab < getFrames.Count())
                    {
                        Thread.Sleep(speed);
    try
    {


                     Dispatcher.Invoke(() =>
                        {
                            gifImage.Source = frames[ab];
                        });
                        if (ab == getFrames.Count - 1&&_Repeat)
                        {
                            ab = 0;

                        }
                        ab++;
    }
     catch{} 



                    }
                });
            }

0

Una alternativa a la animación en espera en WPF es:

 <ProgressBar Height="20" Width="100" IsIndeterminate="True"/>

Mostrará una barra de progreso animada.


1
La pregunta no es necesariamente sobre una animación en espera, sino sobre GIF animados en general. Obviamente, eso podría ser para una animación en espera, en cuyo caso esta podría ser una alternativa adecuada. Pero podría con la misma facilidad para cualquier cantidad de otras necesidades de los medios.
Jeremy Caney
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.