如何在WPF中使动态gif图像正常工作?

246

我应该使用哪种控件类型-ImageMediaElement等?


6
以下是最近这些解决方案的摘要。我使用的是VS2015进行实现。Dario提交的GifImage类很好用,但是我的一些gif出现了伪影。Pradip Daunde和nicael提供的MediaElement方法在预览区域可以工作,但是在运行时,我的所有gif都无法渲染。IgorVaschuk和SaiyanGirl的WpfAnimatedGif解决方案非常好且没有问题,但需要安装第三方库(显然)。我没有尝试其余的解决方案。 - Heath Carroll
21个回答

248

我无法使此问题中最受欢迎的回答(由Dario提供)正常工作。 结果是奇怪的,带有奇怪伪影的卡顿动画。 到目前为止我找到的最佳解决方案是: 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


14
这个方法很有效,而且实现起来不到60秒就完成了。谢谢! - Ryan Sorensen
3
在我看来,这个答案比任何流行的答案都要好得多,特别是因为它不依赖于您使用C#。 - Jamie E
11
这比被接受的答案好多了:使用GIF元数据,不卡顿,是一个NuGet包,适用于任何编程语言。我希望stackoverflow允许对被接受的答案进行不信任投票。 - John Gietzen
7
公共服务通知:WpfAnimatedGif的作者已经将其项目重启为XamlAnimatedGif,并支持WPF、Windows Store(Win8)、Windows 10和Silverlight:https://github.com/XamlAnimatedGif/XamlAnimatedGif。 - josh2112
5
这里的img是什么意思? - amit jha
显示剩余12条评论

114
我发布了一个解决方案,扩展了图像控件并使用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" />

3
这个方案可行,而且对于XBAP应用程序更好,因为您不需要额外的引用。 - Massimiliano
1
+1,干得好!但是它没有考虑到图像的实际帧持续时间...如果你能找到一种读取这些信息的方法,你可以改变代码使用Int32AnimationUsingKeyFrames - Thomas Levesque
7
实际上,GIF 的帧率是恒定的,所以你并不需要关键帧... 你可以使用 gf.Frames[0].MetaData.GetQuery("/grctlext/Delay") 读取帧速率(以百分之一秒为单位返回帧持续时间的 ushort 值)。 - Thomas Levesque
3
@vidstige,是的,我不记得当时为什么发表了这个评论(差不多两年前)。我知道每一帧的延迟时间可能会有所不同,而我的WPF动画GIF库会正确地考虑到这一点。 - Thomas Levesque
2
我尝试实现上述解决方案。GIF 图像不流畅且失去了颜色。 - user3260977
显示剩余9条评论

43

这个小应用怎么样:

后台代码:

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
不错!简短的代码,工作得很好。我不敢相信它没有更多的赞。 - wip
2
最佳答案… 应该放在顶部! 我能够在没有任何代码的情况下让它工作 - 只需使用这个<MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" > - MyGifFile只是我的动画GIF文件的文件名(和路径)。 - Anthony Nichols
天啊,为什么还要绑定到 ListBox,或者根本不需要绑定呢?我试过不绑定,只是把文件路径放在 Source 中,它会出现,但不会动画。如果我使用绑定,即使使用 ListBox,它也根本不会出现 - 它会给我一个异常,说我的文件路径不正确,尽管它是我出现时使用的相同路径。 - vapcguy
1
更新时间太长,每次进入视图都需要更新。 - Yola
对我来说没起作用。我的gif是一个旋转的立方体。它显示gif中的所有帧,因此立方体的角落从焦点帧的背景中显示出来。如果gif帧都是相同的形状(如旋转的圆形),那么它就可以正常工作。 - stymie2

39

我也进行了搜索,并在旧的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控件,但也提到它是一个相对较重的控件,因此有许多替代方案,包括至少两个基于Image控件的自制控件,所以这是最简单的。


你的链接好像已经失效了,是不是指的是这个帖子:http://social.msdn.microsoft.com/Forums/vstudio/en-US/9fa8a2e5-3360-4463-ab4b-a2db8b4d7bea/how-to-make-animated-gif-image-move-in-picturebox? - wip
@wil:不,因为它没有提到“MediaElement”。感谢您提供链接的通知。我很惊讶它能持续这么久。 - Joel B Fant
2
当添加集成引用时,它在我的用户界面中的名称为WindowsFormsIntegration,没有点号:http://i.imgur.com/efMiC23.png - yu yang Jian
忘了提到您还需要引用 System.Drawing。如果您直接将其定向到文件位置,则不是设置 .Image 属性,而是设置 .ImageLocation。最终我搞定了,但这个答案应该包括这些细节。我提交了编辑。 - vapcguy
我发现如果不将相同的尺寸应用于 WindowsFormsHostGridStackPanel,就无法为 PictureBox 应用高度/宽度: Index was outside the bounds of the array - vapcguy
显示剩余5条评论

20

如果您使用<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" />

如果您的文件被打包到应用程序中,您可以使用DataBinding作为源,并在代码中查找路径:public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");。请确保将文件设置为Build=Content并复制到输出目录。 - The Muffin Man
我使用了这种方法,因为 WpfAnimatedGif NuGet 包在我的电脑上表现不佳 - 在 CPU 负载较重时似乎会出现故障。我将 gif 设置为 Build=Resource,并使用相对路径从窗口所在的文件夹设置源,例如 Source="../../Images/Rotating-e.gif"。对我来说效果很好,无需第三方 DLL。 - Richard Moore
2
这是目前最简单的解决方案。但它的问题在于,一旦扫描完动画 GIF 的所有帧,动画就会停止。而且没有办法让 GIF 从第0帧重新开始播放动画或无限循环。至少,我还没有找到使用<MediaElement />的方法。 - BoiseBaked
此外,<MediaElement /> 非常缓慢,并且其方法之间存在线程竞争问题。唉... - BoiseBaked

11

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

/// <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
这段代码是我项目的一部分。我是一名在俄罗斯工作的俄罗斯开发者。因此,注释也是用俄语编写的。并不是世界上每个项目都是“美式英语”项目,Corey。 - Mike Eshva
2
我尝试使用您的代码和以下标记:<local:AnimatedImage Source="/Resources/ajax-loader.gif" />,但目前还没有任何反应。 - Sonic Soul
如果我将其更改为使用JPEG格式,它会显示静态图像,但不是GIF。顺便说一句,代码不错。 - Sonic Soul
太棒了,我需要一个解决方案,可以从资源字典中获取GIF -> BitmapImage -> 动画GIF。这就是它! - m.t.bennett

10

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

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

    PM > Install-Package WpfAnimatedGif

接下来,将此代码片段放入 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


4
这是与@IgorVaschuk在2012年6月给出的答案相同(但不那么详细),目前根据投票排名来看是第二位的解决方案。 - Heath Carroll

6

我修改了Mike Eshva的代码,并使其更好地运行。您可以将其与1帧jpg、png、bmp或多帧gif一起使用。如果您想将uri绑定到控件,请绑定UriSource属性;如果您想绑定任何内存流,请绑定Source属性,它是一个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
}

这是一个自定义控件。您需要在WPF应用程序项目中创建它,并删除样式中的模板覆盖。


1
我只需要将UriSource设置为pack://application:,,,/Images/loader.gif。在运行时,将UriSource或Source设置为相对Uri会失败。 - Farzan
是的,我已经尝试过了,但是出现了异常。它不支持相对URI。 - SuperJMN

5

基本上与上面的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了。) - CodeMouse92
我并不认为这是一个好主意,因为选择WPF的主要原因之一是因为它的显示缩放功能。你最终会得到一个不正确缩放的图像,从而产生一个瑕疵。 - The Muffin Man

3
感谢您的帖子,Joel。它帮助我解决了WPF不支持动态GIF的问题。只需添加一些代码,因为我在设置pictureBoxLoading.Image属性时遇到了很多麻烦,这是由于Winforms API引起的。
我必须将我的动态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);
   }
}

回复:当我试图处理流时 根据MSDN的说明,使用流的位图必须使流在位图的生命周期内保持活动状态。解决方法是要么冻结(Freeze)位图,要么克隆(Clone)位图。 - Jesse Chisholm
1
他只需要说设置.ImageLocation而不是.Image。他使用了错误的方法。.ImageLocation基于Visual Studio项目的根目录工作,所以如果你有一个名为Images的文件夹,你的路径就是imgBox.ImageLocation = "/Images/my.gif";。如果你有一个名为Views的文件夹,在那里你有一个将显示图像的视图,要返回到Images,你必须使用2个点:imgBox.ImageLocation = "../Images/my.gif"; - vapcguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接