题 如何让动画GIF在WPF中工作?


我应该使用什么样的控制类型 - ImageMediaElement等等?


183
2017-10-17 02:19


起源


以下是以下解决方案的最新摘要。我使用VS2015实现了这些。 Dario提交的GifImage类工作得很好,但我的一些GIF是artifacted。 Pradip Daunde和nicael的MediaElement方法似乎在预览区域中工作,但在运行时没有渲染我的GIF。 IgorVaschuk和SaiyanGirl的WpfAnimatedGif解决方案运行良好,没有问题,但需要安装第三方库(显然)。我没有尝试其余的。 - Heath Carroll


答案:


我无法得到这个问题(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);

170
2018-06-14 13:22



这很好用,实施时间不到60秒。谢谢! - Ryan Sorensen
比任何流行的IMO更好的答案,特别是因为它不依赖于你使用C# - Jamie E
这比接受的答案要好得多:使用gif的元数据,不是不稳定的,是NuGet包,是语言无关的。我希望stackoverflow允许对接受的答案投不信任票。 - John Gietzen
我在这个upvote按钮上浏览了chuck norris。 - user3426711
公共服务公告:WpfAnimatedGif的作者已将其项目“重启”为XamlAnimatedGif,它支持WPF,Windows Store(Win8),Windows 10和Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif - josh2112


我发布了扩展图像控件和使用Gif解码器的解决方案。 gif解码器具有帧属性。我动画了 FrameIndex 属性。事件 ChangingFrameIndex 将source属性更改为对应的框架 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" />

101
2017-07-15 21:58



这个适用于XBAP应用程序,因为您不需要额外的引用。 - Max Galkin
这很酷。通过将构造函数代码放在“Initialized”事件中并引入Uri属性,此控件也可以放在XAML文件中。 - flq
+1,好一个!但是,它不会考虑图像的实际帧持续时间...如果您可以找到一种方法来读取该信息,您可以更改代码以使用 Int32AnimationUsingKeyFrames - Thomas Levesque
实际上,帧率对于GIF来说是不变的,所以你毕竟不需要关键帧......你可以用它来读取帧率 gf.Frames[0].MetaData.GetQuery("/grctlext/Delay") (返回一个ushort,它是数百秒内的帧持续时间) - Thomas Levesque
@vidstige,是的,我不记得为什么我当时(差不多2年前)做了这个评论。我知道每帧的延迟可能不同,而我的 WPF动画GIF 图书馆正确地考虑了它。 - Thomas Levesque


我也进行了搜索,并在旧的MSDN论坛上的一个帖子中找到了几个不同的解决方案。 (链接不再有效,所以我将其删除)

最简单的执行似乎是使用WinForms PictureBox 控制,并像这样(从线程中改变了一些东西,大部分都是相同的)。

添加引用 System.Windows.FormsWindowsFormsIntegration,和 System.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 控制,所以这是最简单的。


33
2017-10-17 20:44



这种方法非常有效。谢谢乔尔。 - ironpaw
使用WindowsFormsHost时,你能把这个主窗口与AllowTransparency =“True”放在一起吗? - Junior M
@Junior:是的,你可以设置 AllowTransparency="True"。是否会产生你想到的结果是另一回事。我自己没有尝试过,但我敢打赌 WindowsFormsHost 根本不会变得透明。剩下的 Window 威力。我想你只需要尝试一下。 - Joel B Fant
由于winform API,我在pictureBoxLoading.Image方面遇到了麻烦。我在下面发布的代码解决了我的问题。谢谢你的解决方案,乔尔! - sondlerd
添加Integration引用时,我的UI中的名称是WindowsFormsIntegration,没有点: i.imgur.com/efMiC23.png - yu yang Jian


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

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>

32
2017-09-02 08:34



太好了!简短的代码,做得很好。我不敢相信它没有更多的赞成。 - wil
最佳答案......应该在顶部!我能够让它在没有任何代码的情况下工作 - 只是这个 <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >  - MyGifFile只是我的动画gif的文件名(和路径)。 - Anthony Nichols
不知道为什么似乎不起作用 - yu yang Jian
Jeez,为什么甚至懒得去绑 ListBox,还是完全绑定?我没有绑定尝试它,只是将文件路径放在源中它出现,但没有动画。如果我使用绑定,即使使用 ListBox,它根本没有出现,对我来说 - 它会给我一个例外,我的文件路径不正确,即使它与我出现时使用的相同。 - vapcguy


如果你使用它很简单 <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" />

13
2018-04-23 13:17



如果您的文件打包在您的应用程序中,您可以使用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,并使用Window所在文件夹中的相对路径设置Source。来源= “../../图像/旋转-e.gif”。对我来说工作得很好,不需要第三方DLL。 - Richard Moore


这是我的动画图像控件版本。您可以使用标准属性Source来指定图像源。我进一步改进了它。我是俄罗斯人,项目是俄语,所以评论也是俄语。但无论如何,你应该能够理解所有内容而不需要评论。 :)

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

10
2017-11-02 09:26



此代码是我的一个项目的一部分。我是在俄罗斯工作的俄罗斯开发人员。所以评论也是用俄语写的。并非世界上每个项目都是“美国 - 英国”项目,Corey。 - Mike Eshva
尝试使用以下标记的代码:<local:AnimatedImage Source =“/ Resources / ajax-loader.gif”/>但到目前为止没有发生任何事情 - Sonic Soul
如果我将其更改为使用jpeg,则会显示静止图像。只是没有GIF。好的代码BTW - Sonic Soul
很棒,我需要一个解决方案,我可以从资源字典中获取GIF - > BitmapImage - >动画GIF。就是这个! - m.t.bennett


我用这个库: http://wpfanimatedgif.codeplex.com/

首先,将库安装到项目中(使用Package Manager Console):

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

我希望有所帮助。

资源: http://wpfanimatedgif.codeplex.com/


8
2018-06-23 14:37



这与2012年6月的@ IgorVaschuk的答案相同(不太详细),目前排名第二的解决方案。 - Heath Carroll
@HeathCarroll你是对的!对不起... - matuuar


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

在XAML中:

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

在Code-Behind中:

public partial class ProgressIcon
{
    public ProgressIcon()
    {
        InitializeComponent();
        var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("My.Namespace.ProgressIcon.gif");
        var image = System.Drawing.Image.FromStream(stream);
        Loaded += (s, e) => _loadingPictureBox.Image = image;
    }
}

5
2017-07-16 01:33



好的补充。从我所知道的,真的精简它。 (也就是说,我现在已经用三年多的时间写过WPF了。) - CodeMouse92
我不认为这是一个好主意,因为你使用WPF的一个主要原因是它的显示缩放。你最终会得到一个无法正确缩放的工件(图像)。 - The Muffin Man


我修改了Mike Eshva的代码,并且我使它更好用。你可以使用1frame jpg png bmp或mutil-frame gif。如果你想将uri绑定到控件,绑定UriSource属性或者你想要绑定任何in-绑定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 App Project中创建它,并删除样式中的模板覆盖。


5
2018-04-10 16:16



我只需要将UriSource设置为pack:// application:,,, / Images / loader.gif。在运行时将UriSource或Source设置为相对Uri失败。 - Farzan
是的,我试过了,我得到了一个例外。它不适用于相对的uris。 - SuperJMN


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

您将使用Image控件。使用以下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>

4
2018-04-01 22:08



看起来这种方法的一个缺点是默认情况下动画即使在折叠之后也会继续,这可能会导致性能下降。 - Lynn
它工作得很棒!!!!!! - Sumukha


感谢你的帖子Joel,它帮助我解决了WPF缺乏对动画GIF的支持。因为Winforms api设置了pictureBoxLoading.Image属性,所以只需添加一些代码就可以了。

我不得不将我的动画gif图像的Build Action设置为“Content”,将Copy to output目录设置为“Copy if newer”或“always”。然后在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);
   }
}

3
2018-06-29 18:56



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