如何获得动画 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 中工作?的主要内容,如果未能解决你的问题,请参考以下文章

如何配置 Hibernate 以在 Karaf JPA 示例中工作?

如何配置 .net 核心 API 以在 IIS 中工作?

如何使用反应测试库模拟 ResizeObserver 以在单元测试中工作

如何设置 Python 脚本以在 Apache 2.0 中工作?

如何从 SuperBible 获取 GLTools 库以在 Ubuntu 中工作?或替代方案? [关闭]

如何修复自定义消息框以在父 python pyqt5 的新线程中工作