Comment faire fonctionner un gif animé dans WPF?


218

Quel type de contrôle dois - je utiliser - Image, MediaElementetc.?


4
Voici un résumé récent des solutions ci-dessous. J'ai implémenté ces derniers en utilisant VS2015. La classe GifImage soumise par Dario a très bien fonctionné, mais certains de mes gifs ont été artefactés. L'approche MediaElement de Pradip Daunde et nicael semble fonctionner dans la zone d'aperçu, mais aucun de mes gifs n'a été rendu pendant l'exécution. La solution WpfAnimatedGif par IgorVaschuk et SaiyanGirl a très bien fonctionné sans problèmes, mais a nécessité l'installation d'une bibliothèque tierce (évidemment). Je n'ai pas essayé le reste.
Heath Carroll le

Réponses:


214

Je n'ai pas pu obtenir la réponse la plus populaire à cette question (ci-dessus par Dario) pour fonctionner correctement. Le résultat a été une animation étrange et saccadée avec des artefacts étranges. Meilleure solution que j'ai trouvée jusqu'à présent: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Vous pouvez l'installer avec NuGet

PM> Install-Package WpfAnimatedGif

et pour l'utiliser, dans un nouvel espace de noms dans la fenêtre où vous souhaitez ajouter l'image gif et l'utiliser comme ci-dessous

<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" />

Le package est vraiment soigné, vous pouvez définir certains attributs comme ci-dessous

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

et vous pouvez également l'utiliser dans votre code:

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

EDIT: prise en charge de Silverlight

Selon le commentaire de josh2112 si vous souhaitez ajouter un support GIF animé à votre projet Silverlight, utilisez github.com/XamlAnimatedGif/XamlAnimatedGif


13
Cela a très bien fonctionné et a pris moins de 60 secondes à mettre en œuvre. Merci!
Ryan Sorensen

3
Meilleure réponse que n'importe laquelle des IMO populaires, d'autant plus qu'elle ne dépend pas de vous en utilisant C #
Jamie E

8
C'est tellement mieux que la réponse acceptée: utilise les métadonnées du gif, n'est pas saccadé, est un paquet NuGet, est indépendant du langage. Je souhaite que stackoverflow permette un vote de défiance dans la réponse acceptée.
John Gietzen

6
Annonce de service public: L'auteur de WpfAnimatedGif a `` redémarré '' son projet sous le nom de XamlAnimatedGif, et il prend en charge WPF, Windows Store (Win8), Windows 10 et Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Qu'y a-t-il imgici?
amit jha

104

Je poste une solution étendant le contrôle d'image et utilisant le décodeur Gif. Le décodeur gif a une propriété frames. J'anime la FrameIndexpropriété. L'événement ChangingFrameIndexchange la propriété source en la trame correspondant à FrameIndex(c'est-à-dire dans le décodeur). Je suppose que le gif a 10 images par seconde.

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

Exemple d'utilisation (XAML):

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

1
Celui-ci fonctionne, et mieux pour les applications XBAP, car vous n'avez pas besoin de références supplémentaires.
Max Galkin

1
C'est super. En plaçant votre code constructeur dans l'événement "Initialized" et en introduisant une propriété Uri, ce contrôle peut également être placé dans un fichier XAML.
flq

1
+1, gentil! Cependant, cela ne prend pas en compte la durée réelle de l'image ... Si vous pouvez trouver un moyen de lire ces informations, vous pouvez changer le code pour utiliser unInt32AnimationUsingKeyFrames
Thomas Levesque

7
En fait, le framerate est constant pour GIF, donc vous n'avez pas besoin d'images clés après tout ... Vous pouvez lire le framerate avec gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(retourne une courte durée qui est la durée de la trame en centaines de secondes)
Thomas Levesque

3
@vidstige, oui, je ne me souviens pas pourquoi j'avais fait ce commentaire à l'époque (il y a presque 2 ans). Je suis conscient que le délai peut être différent pour chaque image, et ma bibliothèque GIF animée WPF en tient correctement compte.
Thomas Levesque

38

Moi aussi, j'ai fait une recherche et trouvé plusieurs solutions différentes dans un seul fil sur les anciens forums MSDN. (le lien ne fonctionnait plus, je l'ai donc supprimé)

Le plus simple à exécuter semble être d'utiliser un PictureBoxcontrôle WinForms , et est allé comme ça (a changé quelques choses du fil, la plupart du même).

Ajoutez d'abord une référence à System.Windows.Forms, WindowsFormsIntegrationet System.Drawingà votre projet.

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

Ensuite, dans le Window_Loadedgestionnaire, vous devez définir la pictureBoxLoading.ImageLocationpropriété sur le chemin du fichier image que vous souhaitez afficher.

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

Le MediaElementcontrôle a été mentionné dans ce fil, mais il est également mentionné qu'il s'agit d'un contrôle plutôt lourd, il y avait donc un certain nombre d'alternatives, y compris au moins 2 contrôles homebrewed basés sur le Imagecontrôle, c'est donc le plus simple.


pouvez-vous mettre cette fenêtre principale avec AllowTransparency = "True" lorsque vous utilisez WindowsFormsHost?
Junior Mayhé

@ Junior: Oui, vous pouvez définir AllowTransparency="True". Que cela produise ou non les résultats que vous avez en tête est une autre question. Je ne l'ai pas essayé moi-même, mais je parierais que WindowsFormsHostcela ne deviendrait pas transparent du tout. Le reste de la Windowpuissance. Vous devrez simplement l'essayer, je pense.
Joel B Fant

J'ai eu des problèmes avec pictureBoxLoading.Image en raison de l'API Winform. J'ai posté un code ci-dessous qui a résolu mon problème. Merci pour votre solution, Joel!
sondlerd

On dirait que ton semblable est mort. Était-ce ce fil ?
essuyez

2
Lors de l'ajout d'une référence d'intégration, son nom dans mon interface utilisateur est WindowsFormsIntegration, sans point: i.imgur.com/efMiC23.png
yu yang Jian

36

Que diriez-vous de cette petite application: code derrière:

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
Agréable ! Code court, faisant bien le travail. Je ne peux pas croire qu'il n'y ait pas plus de votes positifs.
essuyez

2
Meilleure réponse ... Devrait être au top! J'ai pu le faire fonctionner sans aucun code derrière - juste cela <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >- Le MyGifFile est juste le nom de fichier (et le chemin) de mon gif animé.
Anthony Nichols

Jeez, pourquoi même prendre la peine de se lier à la ListBox, ou de se lier du tout? Je l'ai essayé sans liaison, mettez simplement le chemin du fichier dans la source et il apparaît, mais ne s'anime pas. Si j'utilise la liaison, même avec le ListBox, cela ne se produit pas du tout, pour moi - cela me donnera une exception que mon chemin de fichier est incorrect, même s'il est le même que celui que j'utilise lorsqu'il apparaît.
vapcguy

La mise à jour prend trop de temps et doit être mise à jour à chaque fois qu'elle apparaît.
Yola

15

C'est très simple si vous utilisez <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" />

Juste au cas où votre dossier est emballé dans votre application , vous pouvez utiliser DataBinding pour la source et trouver le chemin dans le code: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Assurez-vous de définir le fichier sur Build = Content et de le copier dans le répertoire de sortie.
The Muffin Man

J'ai utilisé cette approche parce que le package WpfAnimatedGif NuGet ne fonctionnait pas bien pour moi - semblait pépin lorsque la charge du processeur était élevée. J'ai défini le gif sur Build = Resource et défini la source en utilisant un chemin relatif à partir du dossier dans lequel se trouvait la fenêtre, par exemple Source = "../../ Images / Rotating-egif". A bien fonctionné pour moi et pas besoin de DLL tierces.
Richard Moore

C'est de loin la solution la plus simple. Mais le problème est qu'une fois que toutes les images du gif animé sont numérisées, l'animation s'arrête. Et il n'y a aucun moyen de faire à nouveau animer le gif à partir de l'image 0. Aucun moyen de redémarrer l'animation ou la boucle pour toujours. Au moins, je n'ai pas trouvé de moyen d'utiliser <MediaElement />.
BoiseBaked

<MediaElement /> est également incroyablement lent et plein de problèmes de course de threads entre ses méthodes. Grrr….
BoiseBaked

10

Voici ma version du contrôle d'image animée. Vous pouvez utiliser la propriété standard Source pour spécifier la source d'image. Je l'ai encore amélioré. Je suis russe, le projet est russe donc les commentaires sont également en russe. Mais de toute façon, vous devriez pouvoir tout comprendre sans commentaires. :)

/// <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
Ce code fait partie d'un de mes projets. Je suis un développeur russe qui travaille en Russie. Les commentaires sont donc également en russe. Tous les projets dans le monde ne sont pas des projets "américano-anglais", Corey.
Mike Eshva

2
essayé d'utiliser votre code avec le balisage suivant: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" /> mais jusqu'à présent, rien ne se passe
Sonic Soul

si je le change en utilisant un jpeg, il montre l'image fixe. tout simplement pas le gif. nice code BTW
Sonic Soul

Brillant, j'avais besoin d'une solution où je pouvais mais un GIF du dictionnaire de ressources -> BitmapImage -> GIF animé. Ça y est!
mtbennett

9

J'utilise cette bibliothèque: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Tout d'abord, installez la bibliothèque dans votre projet (à l'aide de la console du gestionnaire de packages):

    PM > Install-Package WpfAnimatedGif

Ensuite, utilisez cet extrait dans le fichier 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" />
        ...

J'espère que ça aide.

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


3
Il s'agit de la même réponse (moins détaillée) que celle de @ IgorVaschuk de juin 2012, actuellement la 2e place en termes de votes.
Heath Carroll

5

Fondamentalement, la même solution PictureBox ci-dessus, mais cette fois avec le code-behind pour utiliser une ressource intégrée dans votre projet:

En XAML:

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

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

Bon ajout. Vraiment rationalise, d'après ce que je peux dire. (Cela dit, je n'ai pas écrit dans WPF depuis plus de trois ans, maintenant.)
CodeMouse92

Je ne pense pas vraiment que ce soit une bonne idée car l'une des principales raisons pour lesquelles vous allez avec WPF est à cause de sa mise à l'échelle de l'affichage. Vous vous retrouverez avec un artefact (l'image) qui ne se redimensionne pas correctement.
The Muffin Man

5

J'ai modifié le code de Mike Eshva, et je l'ai fait mieux fonctionner. Vous pouvez l'utiliser avec 1frame jpg png bmp ou mutil-frame gif.Si vous voulez lier un uri au contrôle, liez les propriétés UriSource ou vous voulez lier n'importe quel in- flux de mémoire que vous liez à la propriété Source qui est un BitmapImage.

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

Il s'agit d'un contrôle personnalisé. Vous devez le créer dans WPF App Project et supprimer le remplacement de modèle avec style.


1
Je devais juste définir UriSource pour pack: // application: ,,, / Images / loader.gif. La définition de UriSource ou Source sur un Uri relatif a échoué lors de l'exécution.
Farzan

Oui, je l'ai essayé et je reçois une exception. Cela ne fonctionne pas avec les uris relatifs.
SuperJMN

3

J'ai eu ce problème, jusqu'à ce que je découvre que dans WPF4, vous pouvez simuler vos propres animations d'images clés. Tout d'abord, divisez votre animation en une série d'images, nommez-les quelque chose comme "Image1.gif", "Image2, gif", etc. Importez ces images dans les ressources de votre solution. Je suppose que vous les avez placés dans l'emplacement de ressource par défaut pour les images.

Vous allez utiliser le contrôle Image. Utilisez le code XAML suivant. J'ai supprimé les éléments non essentiels.

<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
Il semble qu'un inconvénient de cette approche est que, par défaut, l'animation continue même après son effondrement, ce qui peut entraîner une baisse des performances.
Lynn

Ce n'est pas DiscreteObjectKeyFrames, c'est DiscreteObjectKeyFrame. Singulier.
jairhumberto

@jairhumberto Je pense que cela peut avoir changé entre les versions. C'est assez ancien (2011), mais j'utilisais effectivement ce code exact dans un projet.
CodeMouse92

3

Merci pour votre message Joel, cela m'a aidé à résoudre l'absence de prise en charge de WPF pour les GIF animés. Je viens d'ajouter un peu de code car j'ai eu beaucoup de mal à définir la propriété pictureBoxLoading.Image en raison de l'API Winforms.

J'ai dû définir l'action de construction de mon image gif animée comme "Contenu" et le répertoire Copier vers la sortie à "Copier si plus récent" ou "toujours". Ensuite, dans MainWindow (), j'ai appelé cette méthode. Le seul problème est que lorsque j'ai essayé de supprimer le flux, cela m'a donné un graphique d'enveloppe rouge au lieu de mon image. Je vais devoir résoudre ce problème. Cela a supprimé la douleur de charger une BitmapImage et de la transformer en Bitmap (ce qui a évidemment tué mon animation car ce n'est plus 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: quand j'ai essayé de disposer du flux Selon le MSDN, un Bitmap qui utilise un Stream doit avoir le Stream en vie pour la durée de vie du Bitmap. La solution consiste à figer ou cloner le bitmap.
Jesse Chisholm

1
Il avait juste besoin de dire de se mettre à la .ImageLocationplace de .Image. Il avait la mauvaise méthode. .ImageLocationfonctionne à partir de la racine du projet Visual Studio, alors disons que vous avez un Imagesdossier, votre chemin est alors imgBox.ImageLocation = "/Images/my.gif";. Si vous avez un dossier appelé Viewsoù vous avez une vue qui affiche l'image, pour revenir à Images, vous auriez à utiliser 2 points: imgBox.ImageLocation = "../Images/my.gif";.
vapcguy

1

J'ai essayé tout le chemin ci-dessus, mais chacun a sa brièveté, et grâce à vous tous, je travaille sur mon propre 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);
            }
    }
}

Usage:

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

Comme cela ne causerait pas de fuite de mémoire et animerait la propre ligne temporelle de l'image gif, vous pouvez l'essayer.


Excellent échantillon. Besoins Initialize mis à jour pour vérifier IsAutoStart, mais sinon, a fonctionné comme un champion!
Steve Danner

1
L'appel explicite de GC.Collect () a un impact horrible sur les performances.
Kędrzu

0

Auparavant, j'ai rencontré un problème similaire, j'avais besoin de lire un .giffichier dans votre projet. J'avais deux choix:

  • en utilisant PictureBox de WinForms

  • en utilisant une bibliothèque tierce, telle que WPFAnimatedGif de codeplex.com.

La version avec PictureBoxne fonctionnait pas pour moi et le projet ne pouvait pas utiliser de bibliothèques externes pour cela. Je l'ai donc fait pour moi Bitmapavec de l'aide ImageAnimator. Parce que, standard BitmapImagene prend pas en charge la lecture des .giffichiers.

Exemple complet:

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

Bitmapne prend pas en charge la directive URI , je charge donc le .giffichier à partir du répertoire actuel.


0

Petite amélioration de la GifImage.Initialize()méthode, qui lit le bon timing de trame à partir des métadonnées 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

Je ne sais pas si cela a été résolu mais la meilleure façon est d'utiliser la bibliothèque WpfAnimatedGid . Il est très facile, simple et direct à utiliser. Il ne nécessite que 2 lignes de code XAML et environ 5 lignes de code C # dans le code derrière.

Vous verrez tous les détails nécessaires sur la façon dont cela peut être utilisé là-bas. C'est ce que j'ai aussi utilisé au lieu de réinventer la roue


0

En plus de la réponse principale qui recommande l'utilisation de WpfAnimatedGif , vous devez ajouter les lignes suivantes à la fin si vous échangez une image avec un Gif pour vous assurer que l'animation s'exécute réellement:

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

Votre code ressemblera donc à:

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

Vérifiez mon code, j'espère que cela vous a aidé :)

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

                            }
                        });
                    }

ou

     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

Une alternative à l'animation en attente dans WPF est:

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

Il affichera une barre de progression animée.


1
La question ne concerne pas nécessairement une animation en attente, elle concerne les GIF animés en général. Évidemment, cela pourrait être pour une animation en attente, auquel cas cela pourrait être une alternative appropriée. Mais il pourrait tout aussi facilement répondre à de nombreux autres besoins médiatiques.
Jeremy Caney
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.