如何获得动画 gif 以在 WPF 中工作?

Posted

技术标签:

【中文标题】如何获得动画 gif 以在 WPF 中工作?【英文标题】:How do I get an animated gif to work in WPF? 【发布时间】:2010-09-17 16:15:10 【问题描述】:

我应该使用什么控件类型 - ImageMediaElement 等?

【问题讨论】:

这里是以下解决方案的最新摘要。我使用 VS2015 实现了这些。 Dario 提交的 GifImage 类效果很好,但是我的一些 gif 被人为地处理了。 Pradip Daunde 和 nicael 的 MediaElement 方法似乎在预览区域中工作,但我的 gif 没有在运行时呈现。 IgorVaschuk 和 SaiyanGirl 的 WpfAnimatedGif 解决方案运行良好,没有问题,但需要安装第三方库(显然)。其余的我没有尝试。 【参考方案1】:

我无法让这个问题(达里奥以上)的最受欢迎答案正常工作。结果是奇怪的、断断续续的动画和奇怪的伪影。 到目前为止我找到的最佳解决方案: https://github.com/XamlAnimatedGif/WpfAnimatedGif

你可以用 NuGet 安装它

PM> Install-Package WpfAnimatedGif

要使用它,请在要添加 gif 图像的窗口的新命名空间中使用它,如下所示

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

这个包真的很整洁,你可以像下面这样设置一些属性

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

您也可以在代码中使用它:

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

编辑:Silverlight 支持

根据 josh2112 的评论,如果您想为 Silverlight 项目添加动画 GIF 支持,请使用 github.com/XamlAnimatedGif/XamlAnimatedGif

【讨论】:

效果很好,实施时间不到 60 秒。谢谢! 比任何流行的 IMO 更好的答案,特别是因为它不依赖于您使用 C# 这比公认的答案要好得多:使用 gif 的元数据,不不稳定,是 NuGet 包,与语言无关。我希望 *** 允许对接受的答案投不信任票。 公共服务公告:WpfAnimatedGif 的作者已经将他的项目“重新启动”为 XamlAnimatedGif,它支持 WPF、Windows Store (Win8)、Windows 10 和 Silverlight:github.com/XamlAnimatedGif/XamlAnimatedGif 这里的img 是什么?【参考方案2】:

我发布了一个扩展图像控制并使用 Gif 解码器的解决方案。 gif 解码器有一个 frames 属性。我为FrameIndex 属性设置了动画。事件ChangingFrameIndex 将源属性更改为与FrameIndex 对应的帧(即在解码器中)。我猜这个 gif 每秒有 10 帧。

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

使用示例 (XAML):

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

【讨论】:

这个很有效,而且更适合 XBAP 应用程序,因为您不需要额外的参考。 这很酷。通过将构造函数代码放入“Initialized”事件并引入 Uri 属性,此控件也可以放入 XAML 文件中。 +1,不错!但是,它没有考虑图像的实际帧持续时间...如果您能找到读取该信息的方法,您可以更改代码以使用 Int32AnimationUsingKeyFrames 其实 GIF 的帧率是恒定的,所以你根本不需要关键帧...你可以用gf.Frames[0].MetaData.GetQuery("/grctlext/Delay") 读取帧率(返回一个 ushort 是数百秒的帧持续时间) @vidstige,是的,我不记得我当时(大约 2 年前)为什么发表此评论。我知道每个帧的延迟可能不同,我的 WPF Animated GIF 库正确考虑了它。【参考方案3】:

这个小应用怎么样: 后面的代码:

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>

【讨论】:

不错!代码短,干得好。我不敢相信它没有更多的赞成票。 最佳答案...应该在顶部!我能够在没有任何代码的情况下让它工作——只是这个&lt;MediaElement LoadedBehavior="Play" Source="Binding MyGifFile" &gt;——MyGifFile 只是我的动画 gif 的文件名(和路径)。 天啊,为什么还要绑定到ListBox,或者根本就绑定?我尝试了没有绑定,只需将文件路径放在Source中,它就会出现,但没有动画。如果我使用绑定,即使使用ListBox,它也不会出现,对我来说 - 它会给我一个例外,即我的文件路径不正确,即使它与我在它出现时使用的路径相同。 更新时间较长,每次进入视野都需要更新。【参考方案4】:

我也进行了搜索,并在旧 MSDN 论坛上的一个线程中找到了几种不同的解决方案。 (链接失效了,我删了)

最简单的执行似乎是使用 WinForms PictureBox 控件,然后像这样(从线程更改了一些东西,大部分都一样)。

首先向您的项目添加对System.Windows.FormsWindowsFormsIntegrationSystem.Drawing 的引用。

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

然后在Window_Loaded 处理程序中,将pictureBoxLoading.ImageLocation 属性设置为要显示的图像文件路径。

private void Window_Loaded(object sender, RoutedEventArgs e)

    pictureBoxLoading.ImageLocation = "../Images/mygif.gif";

该线程中提到了MediaElement 控件,但也提到它是一个相当繁重的控件,因此有许多替代方案,包括至少 2 个基于Image 控件的自制控件,所以这是最简单的。

【讨论】:

在使用 WindowsFormsHost 时,你能把这个主窗口设置为 AllowTransparency="True" 吗? @Junior:是的,你可以设置AllowTransparency="True"。这是否会产生您想要的结果是另一回事。我自己没有尝试过,但我敢打赌WindowsFormsHost 根本不会变得透明。 Window 的其余部分可能会。我想你只需要尝试一下。 由于 winform API,我在使用 pictureBoxLoading.Image 时遇到了问题。我在下面发布了解决我的问题的代码。感谢您的解决方案,乔尔! 看起来你的喜欢已经死了。是this thread吗? 添加集成引用时,它在我的 UI 中的名称是 WindowsFormsIntegration,不带点:i.imgur.com/efMiC23.png【参考方案5】:

如果你使用&lt;MediaElement&gt;,它非常简单:

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

【讨论】:

万一您的文件打包在您的应用程序中,您可以使用 DataBinding 作为 Source 并在代码中找到路径:public string SpinnerLogoPath =&gt; Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");。确保将文件设置为 Build=Content 并复制到输出目录。 我使用这种方法是因为 WpfAnimatedGif NuGet 包对我来说效果不佳 - 在 CPU 负载较重时似乎会出现故障。我将 gif 设置为 Build=Resource 并使用 Window 所在文件夹的相对路径设置 Source,例如来源="../../Images/Rotating-e.gif"。对我来说效果很好,不需要第 3 方 DLL。 这是迄今为止最简单的解决方案。但问题在于,一旦扫描了动画 gif 的所有帧,动画就会停止。并且没有办法让 gif 再次从第 0 帧开始制作动画。无法重新启动动画或永远循环。至少,我还没有找到使用 的方法。 另外, 速度慢得令人难以置信,并且在其方法之间充满了线程竞争问题。咕噜……【参考方案6】:

这是我的动画图像控制版本。您可以使用标准属性 Source 来指定图像源。我进一步改进了它。我是俄罗斯人,项目是俄罗斯人,所以 cmets 也是俄罗斯人。但无论如何,你应该能够在没有 cmets 的情况下理解一切。 :)

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

【讨论】:

这段代码是我的一个项目的一部分。我是在俄罗斯工作的俄罗斯开发人员。所以 cmets 也是俄语的。并非世界上的每个项目都是“美式英语”项目,Corey。 尝试使用带有以下标记的代码: 但到目前为止没有任何反应 如果我将其更改为使用 jpeg,它会显示静止图像。只是不是 gif。不错的代码顺便说一句 太棒了,我需要一个解决方案,我只能从资源字典 -> BitmapImage -> 动画 GIF 中获取 GIF。就是这个!【参考方案7】:

我使用这个库:https://github.com/XamlAnimatedGif/WpfAnimatedGif

首先,将库安装到您的项目中(使用包管理器控制台):

    PM > Install-Package WpfAnimatedGif

然后,使用这个 sn-p 进入 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" />
        ...

希望对你有帮助。

来源:https://github.com/XamlAnimatedGif/WpfAnimatedGif

【讨论】:

这与@IgorVaschuk 2012 年 6 月的答案相同(不那么详细),目前在投票方面排名第二。【参考方案8】:

我修改了 Mike Eshva 的代码,使它工作得更好。您可以将它与 1frame jpg png bmp 或 mutil-frame gif 一起使用。如果要将 uri 绑定到控件,请绑定 UriSource 属性或者您想要绑定您绑定作为 BitmapImage 的 Source 属性的任何内存流。

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

这是一个自定义控件。您需要在 WPF App Project 中创建它,并删除样式中的 Template override。

【讨论】:

我只需将 UriSource 设置为 pack://application:,,,/Images/loader.gif。将 UriSource 或 Source 设置为相对 Uri 在运行时失败。 是的,我已经尝试过了,但遇到了异常。它不适用于相对 uri。【参考方案9】:

与上面的 PictureBox 解决方案基本相同,但这次使用代码隐藏在您的项目中使用嵌入式资源:

在 XAML 中:

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

在代码隐藏中:

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;
    

【讨论】:

很好的补充。据我所知,确实简化了它。 (也就是说,我已经三年多没有用 WPF 写过东西了。) 我真的不认为这是一个好主意,因为使用 WPF 的主要原因之一是它的显示缩放。您最终会得到一个无法正确缩放的工件(图像)。【参考方案10】:

我遇到了这个问题,直到我发现在 WPF4 中,您可以模拟自己的关键帧图像动画。首先,将您的动画拆分为一系列图像,将它们命名为“Image1.gif”、“Image2,gif”等。将这些图像导入您的解决方案资源。我假设您将它们放在图像的默认资源位置。

您将使用图像控件。使用以下 XAML 代码。我已经删除了非必需品。

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

【讨论】:

这种方法的一个缺点似乎是,默认情况下,即使在折叠后动画也会继续,这可能会导致性能下降。 不是 DiscreteObjectKeyFrames,是 DiscreteObjectKeyFrame。单数。 @jairhumberto 我认为这可能在版本之间发生了变化。这已经很老了(2011 年),但我确实在项目中使用了这个确切的代码。【参考方案11】:

感谢您的帖子 Joel,它帮助我解决了 WPF 不支持动画 GIF 的问题。只需添加一点代码,因为由于 Winforms api,我有一段时间设置 pictureBoxLoading.Image 属性。

我必须将动画 gif 图像的构建操作设置为“内容”,并将复制到输出目录设置为“如果更新则复制”或“始终”。然后在 MainWindow() 我调用了这个方法。唯一的问题是,当我尝试处理流时,它给了我一个红包图形而不是我的图像。我必须解决这个问题。这消除了加载 BitmapImage 并将其更改为 Bitmap 的痛苦(这显然杀死了我的动画,因为它不再是 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:当我试图处理流时 根据 MSDN,使用 Stream 的位图必须使流在位图的生命周期内保持活动状态。解决方法是冻结或克隆位图。 他只需要说设置.ImageLocation而不是.Image。他的方法不对。 .ImageLocation 在 Visual Studio 项目的根目录下工作,假设您有一个 Images 文件夹,那么您的路径就是 imgBox.ImageLocation = "/Images/my.gif";。如果您有一个名为Views 的文件夹,其中有一个可以显示图像的视图,要返回到Images,您必须使用两个点:imgBox.ImageLocation = "../Images/my.gif";【参考方案12】:

上面的方法我都试过了,但各有各的不足,感谢大家,我自己制作了自己的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);
            
    

用法:

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

因为它不会导致内存泄漏,并且它会为gif图像自己的时间线设置动画,你可以尝试一下。

【讨论】:

优秀的样品。需要更新初始化以检查 IsAutoStart,但除此之外,工作就像一个冠军! 显式调用 GC.Collect() 会对性能产生可怕的影响。 这是最好的答案。工作得非常好,并且在像 nuget 库一样制作动画时不会消耗大量内存。有点紧张,看看能不能解决【参考方案13】:

WPF 中等待动画的替代方法是:

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

它将显示一个动画进度条。

【讨论】:

问题不一定是关于等待动画的问题——它通常是关于动画 GIF 的问题。显然,这可能用于等待动画,在这种情况下,这可能是一个合适的替代方案。但它也可以轻松满足任何其他媒体需求。 我在数据绑定和渲染完成时显示进度条 - 但进度条的动画几乎被冻结,因此尝试使用 gif 动画并跳跃它比进度条具有更高的渲染优先级。数据绑定具有相当高的调度程序优先级。这导致了这篇 SO 帖子,建议使用进度条而不是 gif 动画。 ;-)【参考方案14】:

以前,我遇到过类似的问题,我需要在你的项目中播放.gif 文件。我有两个选择:

使用 WinForms 中的 PictureBox

使用第三方库,例如来自codeplex.com.的WPFAnimatedGif

PictureBox 的版本对我不起作用,并且该项目无法使用外部库。所以我通过BitmapImageAnimator 的帮助下为自己制作了它。因为,标准的BitmapImage 不支持播放.gif 文件。

完整示例:

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

Bitmap 不支持 URI 指令,所以我从当前目录加载.gif 文件。

【讨论】:

【参考方案15】:

GifImage.Initialize() 方法的小改进,该方法从 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;
    

【讨论】:

【参考方案16】:

我不确定这是否已解决,但最好的方法是使用WpfAnimatedGid library。它使用起来非常简单、简单且直接。它只需要 2 行 XAML 代码和大约 5 行 C# 代码在后面的代码中。

您将在那里看到如何使用它的所有必要细节。这也是我使用的,而不是重新发明***

【讨论】:

【参考方案17】:

除了推荐使用 WpfAnimatedGif 的主要响应之外,如果您要用 Gif 交换图像,则必须在最后添加以下行以确保动画实际执行:

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

所以你的代码看起来像:

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

【讨论】:

【参考方案18】:

检查我的代码,希望对你有帮助:)

         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



                            
                        );
                    

     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 



                    
                );
            

【讨论】:

【参考方案19】:

我建议使用 WebBrowser 控件。

如果 gif 在网络上,可以在 XAML 中设置来源:

<WebBrowser Source="https://media.giphy.com/media/Ent2j55lyQipa/giphy.gif" />

如果是本地文件,您可以从代码隐藏中创建源代码。

XAML:

<WebBrowser x:Name="WebBrowser" />

代码隐藏:

private void Window_Loaded(object sender, RoutedEventArgs e)

    string curDir = Directory.GetCurrentDirectory();
    this.WebBrowser.Source = new Uri(String.Format("file:///0/10-monkey.gif", curDir));

【讨论】:

以上是关于如何获得动画 gif 以在 WPF 中工作?的主要内容,如果未能解决你的问题,请参考以下文章