Come faccio a far funzionare una GIF animata in WPF?


218

Che tipo di controllo dovrei usare - Image, MediaElemente così via?


4
Ecco un recente riepilogo delle soluzioni seguenti. Li ho implementati usando VS2015. La classe GifImage presentata da Dario ha funzionato alla grande, ma alcune delle mie gif sono state manufatte. L'approccio MediaElement di Pradip Daunde e Nicica sembra funzionare nell'area di anteprima, ma nessuna delle mie gif è stata riprodotta durante il runtime. La soluzione WpfAnimatedGif di IgorVaschuk e SaiyanGirl funzionava perfettamente senza problemi ma richiedeva l'installazione di una libreria di terze parti (ovviamente). Non ho provato il resto.
Heath Carroll,

Risposte:


214

Non sono riuscito a far funzionare correttamente la risposta più popolare a questa domanda (sopra di Dario). Il risultato fu un'animazione strana e increspata con strani artefatti. La migliore soluzione che ho trovato finora: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Puoi installarlo con NuGet

PM> Install-Package WpfAnimatedGif

e per usarlo, in un nuovo spazio dei nomi nella finestra in cui si desidera aggiungere l'immagine gif e usarla come di seguito

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

Il pacchetto è davvero pulito, puoi impostare alcuni attributi come di seguito

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

e puoi usarlo anche nel tuo codice:

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

EDIT: supporto Silverlight

Secondo il commento di josh2112 se si desidera aggiungere il supporto GIF animato al progetto Silverlight, utilizzare github.com/XamlAnimatedGif/XamlAnimatedGif


13
Funzionava alla grande e impiegava meno di 60 secondi per essere implementata. Grazie!
Ryan Sorensen,

3
Risposta decisamente migliore di una delle più popolari IMO, soprattutto perché non si basa su di te usando C #
Jamie E

8
Questo è molto meglio della risposta accettata: usa i metadati di gif, non è instabile, è un pacchetto NuGet, è agnostico nel linguaggio. Vorrei che StackOverflow consentisse un voto senza fiducia nella risposta accettata.
John Gietzen,

6
Annuncio di servizio pubblico: l'autore di WpfAnimatedGif ha "riavviato" il suo progetto come XamlAnimatedGif e supporta WPF, Windows Store (Win8), Windows 10 e Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Cosa c'è imgqui?
Amit jha

104

Pubblico una soluzione che estende il controllo dell'immagine e usa Gif Decoder. Il decoder gif ha una proprietà frames. Animare la FrameIndexproprietà. L'evento ChangingFrameIndexmodifica la proprietà source nel frame corrispondente al FrameIndex(cioè nel decoder). Immagino che la gif abbia 10 fotogrammi al secondo.

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

Esempio di utilizzo (XAML):

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

1
Questo funziona, e meglio per le app XBAP, perché non hai bisogno di riferimenti aggiuntivi.
Max Galkin,

1
Questo è figo. Inserendo il codice del costruttore nell'evento "Inizializzato" e introducendo una proprietà Uri, questo controllo può anche essere inserito in un file XAML.
flq

1
+1, bello! Tuttavia, non tiene conto della durata effettiva del fotogramma dell'immagine ... Se riesci a trovare un modo per leggere tali informazioni, puoi modificare il codice per utilizzare unInt32AnimationUsingKeyFrames
Thomas Levesque, del

7
In realtà, il framerate è costante per GIF, quindi non hai bisogno di fotogrammi chiave dopo tutto ... Puoi leggere il framerate con gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(restituisce un ushort che è la durata del fotogramma in centinaia di secondi)
Thomas Levesque il

3
@vidstige, sì, non ricordo perché avevo fatto questo commento in quel momento (quasi 2 anni fa). Sono consapevole che il ritardo può essere diverso per ciascun fotogramma e la mia libreria GIF animata WPF lo tiene correttamente in considerazione.
Thomas Levesque,

38

Anch'io ho fatto una ricerca e ho trovato diverse soluzioni in un solo thread nei vecchi forum MSDN. (il link non ha più funzionato, quindi l'ho rimosso)

Il più semplice da eseguire sembra essere usare un PictureBoxcontrollo WinForms , ed è andato così (ha cambiato alcune cose dal thread, la maggior parte lo stesso).

Aggiungi prima un riferimento a System.Windows.Forms, WindowsFormsIntegratione System.Drawingal tuo progetto.

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

Quindi, nel Window_Loadedgestore, imposteresti la pictureBoxLoading.ImageLocationproprietà sul percorso del file immagine che vuoi mostrare.

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

Il MediaElementcontrollo è stato menzionato in quel thread, ma si dice anche che è un controllo piuttosto pesante, quindi c'erano un numero di alternative, tra cui almeno 2 controlli homebrewed basati sul Imagecontrollo, quindi questo è il più semplice.


puoi mettere questa finestra principale con AllowTransparency = "True" quando usi WindowsFormsHost?
Junior Mayhé,

@Junior: Sì, puoi impostare AllowTransparency="True". Se questo produrrà o meno i risultati che hai in mente è un'altra questione. Non l'ho provato io stesso, ma scommetterei che WindowsFormsHostnon sarebbe diventato affatto trasparente. Il resto della Windowpotenza. Dovrai semplicemente provarlo, credo.
Joel B Fant,

Ho avuto problemi con pictureBoxLoading.Image a causa dell'API winform. Di seguito ho pubblicato il codice che ha risolto il mio problema. Grazie per la tua soluzione, Joel!
sondlerd,

Sembra che il tuo tipo sia morto. Era questa discussione ?
pulire il

2
Quando si aggiunge il riferimento di integrazione, il suo nome nell'interfaccia utente è WindowsFormsIntegration, senza punto: i.imgur.com/efMiC23.png
yu yang Jian

36

Che ne dici di questa piccola app: Codice dietro:

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
Bello ! Codice funzione, facendo bene il lavoro. Non posso credere che non abbia più voti.
cancella il

2
La migliore risposta ... Dovrebbe essere al top! Sono stato in grado di farlo funzionare senza alcun codice - solo questo <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >- MyGifFile è solo il nome del file (e il percorso) della mia gif animata.
Anthony Nichols,

Accidenti, perché nemmeno preoccuparsi di legarsi al ListBox, o legare affatto? L'ho provato senza associazione, ho appena messo il percorso del file nel Sorgente e sembra, ma non si anima. Se uso l'associazione, anche con il ListBox, non mi viene affatto, mi darà un'eccezione che il mio percorso del file non è corretto, anche se è lo stesso che uso per quando appare.
vapcguy,

L'aggiornamento richiede molto tempo e deve essere aggiornato ogni volta che viene visualizzato.
Yola,

15

È molto semplice se si utilizza <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" />

Solo nel caso in cui il file viene compresso nella vostra applicazione è possibile utilizzare DataBinding per l'origine e trovare il percorso nel codice: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Assicurati di impostare il file su Build = Content e copia nella directory di output.
The Muffin Man,

Ho usato questo approccio perché il pacchetto NuGet di WpfAnimatedGif non funzionava bene per me - sembrava avere problemi con un carico pesante della CPU. Ho impostato gif su Build = Resource e ho impostato Source usando un percorso relativo dalla cartella in cui si trovava Window ad es. Source = "../../ Immagini / Rotating-egif". Ha funzionato bene per me e non ho bisogno di DLL di terze parti.
Richard Moore,

Questa è di gran lunga la soluzione più semplice. Ma il problema è che una volta scansionati tutti i fotogrammi della gif animata, l'animazione si interrompe. E non c'è modo di far animare nuovamente la GIF dal frame 0. Non c'è modo di riavviare l'animazione o il loop per sempre. Almeno, non ho trovato un modo usando <MediaElement />.
BoiseBaked

Anche <MediaElement /> è incredibilmente lento e pieno di problemi di thread racing tra i suoi metodi. Grrr ....
Boise

10

Ecco la mia versione del controllo dell'immagine animata. È possibile utilizzare la sorgente di proprietà standard per specificare l'origine dell'immagine. L'ho ulteriormente migliorato. Sono russo, il progetto è russo, quindi anche i commenti sono in russo. Ma comunque dovresti essere in grado di capire tutto senza commenti. :)

/// <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
Questo codice fa parte di uno dei miei progetti. Sono uno sviluppatore russo che lavora in Russia. Quindi i commenti sono anche in russo. Non ogni progetto al mondo è un progetto "americano-inglese", Corey.
Mike Eshva,

2
provato ad usare il tuo codice con il seguente markup: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" /> ma finora non sta succedendo nulla
Sonic Soul

se lo cambio usando un jpeg, mostra l'immagine fissa. semplicemente non la gif. bel codice BTW
Sonic Soul

Fantastico, avevo bisogno di una soluzione in cui non potevo che una GIF dal Dizionario delle risorse -> BitmapImage -> GIF animate. Questo è!
mtbennett,

9

Uso questa libreria: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Innanzitutto, installa la libreria nel tuo progetto (usando la console di Package Manager):

    PM > Install-Package WpfAnimatedGif

Quindi, utilizza questo frammento nel file 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" />
        ...

Spero aiuti

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


3
Questa è la stessa (meno dettagliata) risposta di quella di @ IgorVaschuk del giugno 2012, attualmente la soluzione al 2 ° posto in termini di voti.
Heath Carroll,

5

Fondamentalmente la stessa soluzione PictureBox sopra, ma questa volta con il code-behind per utilizzare una risorsa integrata nel progetto:

In XAML:

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

Nel codice dietro:

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

Buona aggiunta. Lo semplifica davvero, da quello che posso dire. (Detto questo, non
scrivo

Non penso proprio che sia una buona idea perché uno dei motivi principali per cui vai con WPF è a causa del suo ridimensionamento del display. Ti ritroverai con un artefatto (l'immagine) che non si ridimensiona correttamente.
The Muffin Man,

5

Ho modificato il codice di Mike Eshva, e l'ho fatto funzionare meglio. Puoi usarlo con 1frame jpg png bmp o mutil-frame gif. Se vuoi associare un uri al controllo, associa le proprietà UriSource o vuoi associare qualsiasi in flusso di memoria che lega la proprietà Source che è 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
}

Questo è un controllo personalizzato. È necessario crearlo nel progetto app WPF ed eliminare la sostituzione del modello con stile.


1
Ho dovuto solo impostare UriSource per comprimere: // application: ,,, / Images / loader.gif. L'impostazione di UriSource o Source su un Uri relativo non è riuscita in fase di esecuzione.
Farzan,

Sì, l'ho provato e ricevo un'eccezione. Non funziona con l'uride relativo.
SuperJMN,

3

Ho avuto questo problema, fino a quando non ho scoperto che in WPF4, puoi simulare le tue animazioni di immagini di fotogrammi chiave. Innanzitutto, dividi l'animazione in una serie di immagini, chiamale come "Image1.gif", "Image2, gif" e così via. Importa quelle immagini nelle risorse della tua soluzione. Presumo che tu li abbia inseriti nella posizione di risorsa predefinita per le immagini.

Stai per utilizzare il controllo immagine. Utilizzare il seguente codice XAML. Ho rimosso i non essenziali.

<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
Sembra che un lato negativo di questo approccio sia che, per impostazione predefinita, l'animazione continua anche dopo essere stata compressa, il che può causare un calo delle prestazioni.
Lynn,

Non è DiscreteObjectKeyFrames, è DiscreteObjectKeyFrame. Singolare.
jairhumberto,

@jairhumberto Penso che potrebbe essere cambiato tra le versioni. Questo è piuttosto vecchio (2011), ma in effetti stavo usando questo codice esatto in un progetto.
CodeMouse92,

3

Grazie per il tuo post Joel, mi ha aiutato a risolvere l'assenza di supporto di WPF per le GIF animate. Ho appena aggiunto un po 'di codice dato che ho avuto un sacco di tempo con l'impostazione della proprietà pictureBoxLoading.Image a causa dell'API Winforms.

Ho dovuto impostare l'azione di costruzione della mia immagine GIF animata come "Contenuto" e la directory Copia nella cartella di output su "Copia se più recente" o "sempre". Quindi in MainWindow () ho chiamato questo metodo. L'unico problema è che quando ho provato a smaltire il flusso, mi ha dato un grafico a busta rossa invece della mia immagine. Dovrò risolvere quel problema. Ciò ha rimosso il problema di caricare un'immagine Bitmap e di cambiarla in una bitmap (che ovviamente ha ucciso la mia animazione perché non è più una 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);
   }
}

ri: quando ho provato a smaltire lo stream Secondo MSDN, una Bitmap che utilizza uno Stream deve mantenere lo Stream attivo per la vita della Bitmap. La soluzione alternativa consiste nel bloccare o clonare la bitmap.
Jesse Chisholm,

1
Doveva solo dire di impostare .ImageLocationinvece di .Image. Aveva il metodo sbagliato. .ImageLocationfunziona dalla radice del progetto Visual Studio, quindi supponiamo che tu abbia una Imagescartella, quindi il tuo percorso è imgBox.ImageLocation = "/Images/my.gif";. Se si dispone di una cartella chiamata Viewsin cui si ha una vista che mostra l'immagine, per tornare fino a Images, si avrebbe dovuto usare 2 punti: imgBox.ImageLocation = "../Images/my.gif";.
vapcguy,

1

Ho provato fino in fondo, ma ognuno ha la sua mancanza e, grazie a tutti voi, ho elaborato il mio 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}" />

Poiché non causerebbe perdita di memoria e animerebbe la propria linea temporale dell'immagine gif, puoi provarla.


Campione eccellente. Deve essere inizializzato aggiornato per verificare IsAutoStart, ma per il resto ha funzionato come un campione!
Steve Danner,

1
Chiamare esplicitamente GC.Collect () ha un impatto orribile sulle prestazioni.
Kędrzu,

0

In precedenza, avevo riscontrato un problema simile, dovevo riprodurre il .giffile nel tuo progetto. Ho avuto due scelte:

  • utilizzando PictureBox da WinForms

  • utilizzando una libreria di terze parti, come WPFAnimatedGif di codeplex.com.

La versione con PictureBoxnon ha funzionato per me e il progetto non ha potuto utilizzare librerie esterne per esso. Quindi ce l'ho fatta Bitmapcon l'aiuto ImageAnimator. Perché, lo standard BitmapImagenon supporta la riproduzione di .giffile.

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

Bitmapnon supporta la direttiva URI , quindi carico il .giffile dalla directory corrente.


0

Piccolo miglioramento del GifImage.Initialize()metodo, che legge il corretto timing dei frame dai metadati 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

Non sono sicuro che sia stato risolto, ma il modo migliore è utilizzare la libreria WpfAnimatedGid . È molto facile, semplice e diretto da usare. Richiede solo 2 righe di codice XAML e circa 5 righe di codice C # nel codice sottostante.

Vedrai tutti i dettagli necessari su come questo può essere usato lì. Questo è quello che ho usato anche invece di reinventare la ruota


0

Aggiungendo alla risposta principale che consiglia l'utilizzo di WpfAnimatedGif , è necessario aggiungere le seguenti righe alla fine se si scambia un'immagine con una Gif per assicurarsi che l'animazione venga effettivamente eseguita:

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

Quindi il tuo codice sarà simile a:

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

Controlla il mio codice, spero che questo ti abbia aiutato :)

         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

Un'alternativa all'animazione in attesa in WPF è:

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

Mostrerà una barra di avanzamento animata.


1
La domanda non è necessariamente quella di un'animazione in attesa, ma di GIF animate in generale. Ovviamente, potrebbe essere un'animazione in attesa, nel qual caso potrebbe essere un'alternativa appropriata. Ma potrebbe altrettanto facilmente per qualsiasi numero di altre esigenze dei media.
Jeremy Caney,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.