Como obtenho um gif animado para trabalhar no WPF?


218

Que tipo de controle que eu deveria usar - Image, MediaElement, etc.?


4
Aqui está um resumo recente das soluções abaixo. Eu implementei isso usando o VS2015. A classe GifImage enviada por Dario funcionou muito bem, mas alguns dos meus gifs foram artefatos. A abordagem MediaElement de Pradip Daunde e nicael parece funcionar na área de visualização, mas nenhum dos meus gifs renderizados durante o tempo de execução. A solução WpfAnimatedGif de IgorVaschuk e SaiyanGirl funcionou muito bem sem problemas, mas exigiu a instalação de uma biblioteca de terceiros (obviamente). Eu não tentei o resto.
— Heath Carroll

Respostas:


214

Não consegui obter a resposta mais popular para esta pergunta (acima, por Dario), para funcionar corretamente. O resultado foi uma animação estranha e irregular com artefatos estranhos. Melhor solução que encontrei até agora: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Você pode instalá-lo com o NuGet

PM> Install-Package WpfAnimatedGif

e para usá-lo, em um novo espaço de nome na janela onde você deseja adicionar a imagem gif e usá-la como abaixo

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

O pacote é realmente legal, você pode definir alguns atributos como abaixo

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

e você também pode usá-lo no seu código:

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

Edição: Suporte Silverlight

Conforme o comentário de josh2112, se você deseja adicionar suporte GIF animado ao seu projeto Silverlight, use github.com/XamlAnimatedGif/XamlAnimatedGif


13
Isso funcionou muito bem e levou menos de 60 segundos para ser implementado. Obrigado!
— 22812 Ryan Sorensen

3
Muito melhor resposta do que qualquer dos mais populares da OMI, especialmente desde que não depende de você usando C #
— Jamie E

8
Isso é muito melhor do que a resposta aceita: usa os metadados do gif, não é instável, é um pacote NuGet, é independente de idioma. Desejo que o stackoverflow permita um voto de confiança na resposta aceita.
— John Gietzen

6
Anúncio de serviço público: o autor do WpfAnimatedGif 'reiniciou' seu projeto como XamlAnimatedGif e suporta WPF, Windows Store (Win8), Windows 10 e Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
— josh2112

2
O que tem imgaqui?
— Amit JAI

104

Postei uma solução estendendo o controle de imagem e usando o Decodificador Gif. O decodificador gif tem uma propriedade de quadros. Eu animar a FrameIndexpropriedade. O evento ChangingFrameIndexaltera a propriedade de origem para o quadro correspondente ao FrameIndex(que está no decodificador). Eu acho que o gif tem 10 quadros por segundo.

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

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

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

        _isInitialized = true;
    }

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

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

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

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

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

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

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

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

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

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

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

        BeginAnimation(FrameIndexProperty, _animation);
    }

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

Exemplo de uso (XAML):

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

1
Este funciona e é melhor para aplicativos XBAP, porque você não precisa de referências adicionais.
— Max Galkin

1
Isso é legal. Ao colocar o código do construtor no evento "Initialized" e introduzir uma propriedade Uri, esse controle também pode ser colocado em um arquivo XAML.
— Flq 9/10/10

1
+1, bom! No entanto, isso não leva em consideração a duração real do quadro da imagem ... Se você puder encontrar uma maneira de ler essas informações, poderá alterar o código para usar umInt32AnimationUsingKeyFrames
— Thomas Levesque

7
Na verdade, a taxa de quadros é constante para o GIF, então você não precisa de quadros-chave, afinal ... Você pode ler a taxa de quadros com gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(retorna um ushort que é a duração do quadro em centenas de segundos)
— Thomas Levesque

3
@vidstige, sim, não me lembro por que fiz esse comentário na época (quase 2 anos atrás). Estou ciente de que o atraso pode ser diferente para cada quadro, e minha biblioteca de GIFs animados do WPF leva isso em consideração corretamente.
— Thomas Levesque

38

Também fiz uma pesquisa e encontrei várias soluções diferentes em apenas um tópico nos antigos fóruns do MSDN. (o link não funcionou mais, por isso eu o removi)

A mais simples de executar parece ser usar um PictureBoxcontrole WinForms e foi assim (alterou algumas coisas do segmento, a maioria do mesmo modo).

Adicione uma referência a System.Windows.Forms, WindowsFormsIntegratione System.Drawingao seu projeto primeiro.

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

Em seguida, no Window_Loadedmanipulador, você definiria a pictureBoxLoading.ImageLocationpropriedade para o caminho do arquivo de imagem que deseja mostrar.

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

O MediaElementcontrole foi mencionado nesse encadeamento, mas também é mencionado que é um controle bastante pesado; portanto, havia várias alternativas, incluindo pelo menos 2 controles homebrewed baseados no Imagecontrole; portanto, é o mais simples.


você pode colocar esta janela principal com AllowTransparency = "True" ao usar WindowsFormsHost?
— Junior Mayhé 14/12/2009

@ Junior: Sim, você pode definir AllowTransparency="True". Se isso produzirá ou não os resultados que você tem em mente é outra questão. Eu mesmo não tentei, mas aposto que WindowsFormsHostisso não se tornaria transparente. O resto do Windowpoder. Você simplesmente terá que tentar, eu acho.
— Joel B Fant

Eu tive problemas com o pictureBoxLoading.Image devido à API do winform. Postei o código abaixo que resolveu meu problema. Obrigado pela sua solução, Joel!
— Sondlerd

Parece que seu like está morto. Foi esse segmento ?
— wip

2
Ao adicionar referência Integração, seu nome em meu UI é WindowsFormsIntegration, sem ponto: i.imgur.com/efMiC23.png
— Yang Yu Jian

36

Que tal esse pequeno aplicativo: Código por trás:

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

XAML:

<Window x:Class="PicViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="175" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <ListBox x:Name="lst" ItemsSource="{Binding Path=Files}"/>
        <MediaElement Grid.Column="1" LoadedBehavior="Play" Source="{Binding ElementName=lst, Path=SelectedItem}" Stretch="None"/>
    </Grid>
</Window>

1
Agradável ! Código curto, fazendo bem o trabalho. Não acredito que ele não tenha mais votos positivos.
— wip

2
Melhor resposta ... Deve estar no topo! Consegui fazê-lo funcionar sem nenhum código por trás - apenas isso <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >- O MyGifFile é apenas o nome do arquivo (e o caminho) do meu gif animado.
— Anthony Nichols

Eita, por que se preocupar em ligar-se ao ListBox, ou ligar-se? Eu tentei sem ligação, basta colocar o caminho do arquivo na fonte e ele aparece, mas não anima. Se eu usar a ligação, mesmo com a ListBox, ela não será exibida para mim - isso me dará uma exceção de que o caminho do meu arquivo está incorreto, mesmo que seja o mesmo que eu uso quando aparece.
— vapcguy

A atualização é demorada e precisa ser atualizada sempre que for exibida.
— Yola

15

É muito simples se você usar <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" />

Apenas no caso de seu arquivo é compactado em seu aplicativo que você pode usar DataBinding para a Fonte e encontrar o caminho no código: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Certifique-se de definir o arquivo como Build = Content e copie para o diretório de saída.
— The Muffin Man

Usei essa abordagem porque o pacote WpfAnimatedGif NuGet não funcionou bem para mim - parecia ter problemas quando estava sob uma carga pesada da CPU. Defino o gif como Build = Resource e defina a Origem usando um caminho relativo da pasta na qual a janela estava, por exemplo, Source = "../../ Images / Rotating-e.g.". Funcionou bem para mim e não há necessidade de DLLs de terceiros.
— Richard Moore

Esta é a solução mais simples de longe. Mas o problema é que, quando todos os quadros do gif animado são digitalizados, a animação para. E não há como fazer o gif animar a partir do quadro 0 novamente. Não há como reiniciar a animação ou fazer um loop para sempre. Pelo menos, não encontrei uma maneira de usar <MediaElement />.
— BoiseBaked

Além disso, <MediaElement /> é incrivelmente lento e cheio de problemas de corrida de threads entre seus métodos. Grrr….
— precisa saber é o seguinte

10

Aqui está minha versão do controle de imagem animada. Você pode usar a propriedade padrão Source para especificar a fonte da imagem. Eu melhorei ainda mais. Eu sou russo, o projeto é russo, então os comentários também estão em russo. Mas de qualquer maneira você deve entender tudo sem comentários. :)

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

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

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

    #endregion

    #region Protected interface

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

        BitmapImage lBitmapImage = aEventArgs.NewValue as BitmapImage;

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

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

        PrepareAnimation(lBitmapImage);
    }

    #endregion

    #region Private properties

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

    #endregion

    #region Private methods

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

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

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

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

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

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

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

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

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

        return lResult;
    }

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

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

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

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

    #endregion

    #region Dependency Properties

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

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

    #endregion
}

15
Este código é parte de um dos meus projetos. Eu sou um desenvolvedor russo que trabalha na Rússia. Então, os comentários também estão em russo. Nem todo projeto no mundo é um projeto "inglês americano", Corey.
— Mike Eshva

2
tentou usar seu código com a seguinte marcação: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" /> mas até agora nada está acontecendo
— o Sonic Alma

se eu mudar para usar um jpeg, ele mostra a imagem estática. apenas não o gif. bom código BTW
— Sonic Soul

Brilhante, eu precisava de uma solução em que pudesse, mas um GIF, no Dicionário de Recursos -> BitmapImage -> GIF animado. É isso!
— mtbennett

9

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

Primeiro, instale a biblioteca em seu projeto (usando o Package Manager Console):

    PM > Install-Package WpfAnimatedGif

Em seguida, use esse trecho no arquivo XAML:

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

Espero que ajude.

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


3
Essa é a mesma resposta (menos detalhada) da resposta de @ IgorVaschuk de junho de 2012, atualmente a solução do 2º lugar em termos de votos.
— Heath Carroll

5

Basicamente, a mesma solução PictureBox acima, mas desta vez com o code-behind para usar um Recurso Incorporado em seu projeto:

No XAML:

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

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

Boa adição. Realmente simplifica, pelo que posso dizer. (Dito isto, eu não tenho escrito em WPF em mais de três anos, agora.)
— CodeMouse92

Realmente não acho que seja uma boa ideia, porque um dos principais motivos pelos quais você escolhe o WPF é o tamanho da exibição. Você terminará com um artefato (a imagem) que não será dimensionado corretamente.
— The Muffin Man

5

Modifiquei o código de Mike Eshva e o fiz funcionar melhor. Você pode usá-lo com 1frame jpg png bmp ou mutil-frame gif. Se você deseja vincular uma uri ao controle, vincule as propriedades UriSource ou deseja fluxo de memória que você liga a propriedade Source, que é uma 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
}

Este é um controle personalizado. Você precisa criá-lo no WPF App Project e excluir a substituição do modelo com estilo.


1
Eu apenas tive que configurar o UriSource para empacotar: // application: ,,, / Images / loader.gif. A configuração de UriSource ou Source como um Uri relativo falhou em tempo de execução.
— Farzan

Sim, eu tentei e estou recebendo uma exceção. Não funciona com uris relativos.
— precisa saber é o seguinte

3

Eu tive esse problema, até descobrir que, no WPF4, você pode simular suas próprias animações de imagem de quadro-chave. Primeiro, divida sua animação em uma série de imagens, nomeie-as como "Image1.gif", "Image2, gif" e assim por diante. Importe essas imagens para os recursos da sua solução. Suponho que você os coloque no local do recurso padrão para imagens.

Você vai usar o controle de imagem. Use o seguinte código XAML. Eu removi os não essenciais.

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

1
Parece que um lado negativo dessa abordagem é que, por padrão, a animação continua mesmo depois de recolhida, o que pode causar um impacto no desempenho.
— Lynn

Não é DiscreteObjectKeyFrames, é DiscreteObjectKeyFrame. Singular.
— jairhumberto 12/02

@jairhumberto Acho que isso pode ter mudado entre as versões. Isso é bastante antigo (2011), mas eu estava realmente usando esse código exato em um projeto.
— CodeMouse92

3

Obrigado pelo seu post, Joel, que me ajudou a resolver a ausência de suporte do WPF para GIFs animados. Apenas adicionando um pouco de código, já que tive um tempo enorme ao definir a propriedade pictureBoxLoading.Image devido à API do Winforms.

Eu tive que definir a Ação de compilação da minha imagem gif animada como "Conteúdo" e o diretório Copiar para saída como "Copiar se mais novo" ou "sempre". Então, na MainWindow (), chamei esse método. O único problema é que, quando tentei descartar o fluxo, ele me deu um gráfico de envelope vermelho em vez da minha imagem. Vou ter que resolver esse problema. Isso removeu a dor de carregar uma BitmapImage e transformá-la em um Bitmap (que obviamente matou minha animação porque não é mais um 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: quando tentei descartar o fluxo De acordo com o MSDN, um bitmap que usa um fluxo deve manter o fluxo ativo durante toda a vida útil do bitmap. A solução alternativa é congelar ou clonar o bitmap.
— Jesse Chisholm

1
Ele só precisava dizer para definir em .ImageLocationvez de .Image. Ele tinha o método errado. .ImageLocationfunciona a partir da raiz do projeto Visual Studio, então digamos que você tenha uma Imagespasta, seu caminho é então imgBox.ImageLocation = "/Images/my.gif";. Se você tem uma pasta chamada Viewsonde você tem uma vista que vai mostrar a imagem, para receber de volta até Images, você teria que usar 2 pontos: imgBox.ImageLocation = "../Images/my.gif";.
— vapcguy

1

Eu tentei todo o caminho acima, mas cada um tem sua falta, e graças a todos vocês, eu trabalho meu próprio GifImage:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows.Controls;
    using System.Windows;
    using System.Windows.Media.Imaging;
    using System.IO;
    using System.Windows.Threading;

    namespace IEXM.Components
    {
    public class GifImage : Image
    {
            #region gif Source, such as "/IEXM;component/Images/Expression/f020.gif"
            public string GifSource
            {
                    get { return (string)GetValue(GifSourceProperty); }
                    set { SetValue(GifSourceProperty, value); }
            }

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

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

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

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

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

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

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

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

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

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

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

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

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

               _source = GetSource();

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

                    Source = _source;
                    InvalidateVisual();
            }

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

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


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

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

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

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

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

Uso:

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

Como não causaria vazamento de memória e animava a própria linha do tempo da imagem gif, você pode experimentá-lo.


Excelente amostra. Precisa inicializar atualizado para verificar IsAutoStart, mas, caso contrário, funcionou como um campeão!
— 21414 Steve Stevener

1
Chamar explicitamente GC.Collect () tem um impacto horrível no desempenho.
— Kędrzu

0

Anteriormente, eu enfrentava um problema semelhante, precisava reproduzir .gifarquivos no seu projeto. Eu tive duas escolhas:

  • usando o PictureBox do WinForms

  • usando uma biblioteca de terceiros, como WPFAnimatedGif de codeplex.com.

A versão com PictureBoxnão funcionou para mim e o projeto não pôde usar bibliotecas externas para isso. Então eu fiz isso por mim mesmo Bitmapcom ajuda ImageAnimator. Porque, o padrão BitmapImagenão suporta a reprodução de .gifarquivos.

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

Bitmapnão suporta a diretiva URI , por isso carrego o .gifarquivo do diretório atual.


0

Pequena melhoria no GifImage.Initialize()método, que lê o tempo de quadro adequado dos metadados do 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

Não tenho certeza se isso foi resolvido, mas a melhor maneira é usar a biblioteca WpfAnimatedGid . É muito fácil, simples e direto de usar. Requer apenas 2 linhas de código XAML e cerca de 5 linhas de código C # no código por trás.

Você verá todos os detalhes necessários de como isso pode ser usado lá. Isto é o que eu também usei em vez de reinventar a roda


0

Adicionando à resposta principal que recomenda o uso de WpfAnimatedGif , você deve adicionar as seguintes linhas no final se estiver trocando uma imagem por um GIF para garantir que a animação seja realmente executada:

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

Portanto, seu código será semelhante 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

Verifique meu código, espero que isso tenha ajudado :)

         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

Uma alternativa para aguardar animação no WPF é:

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

Ele mostrará uma barra de progresso animada.


1
A pergunta não é necessariamente sobre uma animação em espera - é sobre GIFs animados em geral. Obviamente, isso poderia ser uma animação em espera; nesse caso, essa poderia ser uma alternativa apropriada. Mas poderia ser tão facilmente para qualquer número de outras necessidades de mídia.
— Jeremy Caney
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.