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.