Как получить анимированный GIF для работы в WPF?


218

Какой тип управления мне следует использовать - Image, MediaElementи т. Д.?


4
Вот недавнее резюме следующих решений. Я реализовал их, используя VS2015. Класс GifImage, представленный Дарио, работал отлично, но некоторые из моих картинок были искажены. Подход MediaElement от Pradip Daunde и nicael, кажется, работает в области предварительного просмотра, но ни один из моих gif-файлов не отображается во время выполнения. Решение WpfAnimatedGif от Игоря Ващука и SaiyanGirl прекрасно работало без проблем, но требовало установки сторонней библиотеки (очевидно). Я не пробовал остальное.
Хит Кэрролл

Ответы:


214

Я не мог заставить самый популярный ответ на этот вопрос (выше Дарио) работать должным образом. В результате получилась странная, прерывистая анимация со странными артефактами. Лучшее решение, которое я нашел до сих пор: 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, если вы хотите добавить поддержку анимированного GIF в свой проект Silverlight, используйте github.com/XamlAnimatedGif/XamlAnimatedGif


13
Это прекрасно работало и заняло менее 60 секунд. Спасибо!
Райан Соренсен

3
Гораздо лучший ответ, чем у любого другого популярного IMO, особенно если учесть, что вы не используете C #
Jamie E

8
Это намного лучше, чем принятый ответ: использует метаданные gif, не изменчив, является пакетом NuGet, не зависит от языка. Я хотел бы, чтобы stackoverflow позволял голосовать за недоверие к принятому ответу.
Джон Гитцен

6
Объявление для публичной службы: автор WpfAnimatedGif «перезагрузил» свой проект как XamlAnimatedGif и поддерживает WPF, Windows Store (Win8), Windows 10 и Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Что imgздесь?
amit jha

104

Я публикую решение, расширяющее возможности управления изображением и использующее Gif Decoder. GIF-декодер имеет свойство кадров. Я оживляю 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" />

1
Это работает, и лучше для приложений XBAP, потому что вам не нужны дополнительные ссылки.
Макс Галкин

1
Это классно. Поместив код конструктора в событие «Initialized» и введя свойство Uri, этот элемент управления также можно поместить в файл XAML.
FLQ

1
+1, приятный! Тем не менее, он не учитывает фактическую продолжительность кадра изображения ... Если вы можете найти способ прочитать эту информацию, вы можете изменить код, чтобы использоватьInt32AnimationUsingKeyFrames
Томас Левеск

7
Фактически, частота кадров постоянна для GIF, поэтому вам не нужны ключевые кадры в конце концов ... Вы можете прочитать частоту кадров с помощью gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(возвращает ushort, который является длительностью кадра в сотнях секунд)
Томас Левеск,

3
@vidstige, да, я не помню, почему я сделал этот комментарий в то время (почти 2 года назад). Я знаю, что задержка может быть разной для каждого кадра, и моя библиотека WPF Animated GIF правильно учитывает это.
Томас Левеск

38

Я тоже сделал поиск и нашел несколько разных решений в одной ветке на старых форумах MSDN. (ссылка больше не работает, поэтому я удалил ее)

Кажется, что проще всего выполнить WinForms PictureBox управления , и все пошло так (изменилось несколько вещей из потока, большинство из них то же самое).

Сначала добавьте ссылку на System.Windows.Forms, WindowsFormsIntegrationи 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элементе управления, так что это самый простой.


Вы можете поставить это главное окно с AllowTransparency = "True" при использовании WindowsFormsHost?
Младший Mayhé

@ Юниор: Да, вы можете установить AllowTransparency="True". Будет ли это давать результаты, которые вы имеете в виду, это другой вопрос. Я сам не пробовал, но держу пари, что WindowsFormsHostэто не станет прозрачным. Остальная часть Windowмощи. Я думаю, тебе просто придется это попробовать.
Джоэл Би Фэнт

У меня были проблемы с pictureBoxLoading.Image из-за winform API. Я разместил код ниже, который решил мою проблему. Спасибо за ваше решение, Джоэл!
sondlerd

Похоже, ваш лайк мертв. Была ли эта тема ?
WIP

2
При добавлении ссылки на интеграцию ее имя в моем пользовательском интерфейсе называется WindowsFormsIntegration, без точки: i.imgur.com/efMiC23.png
yu yang Jian

36

Как насчет этого крошечного приложения: Код позади:

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.
Энтони Николс

Боже, зачем вообще связываться с ListBox, или связывать вообще? Я пробовал без привязки, просто поместите путь к файлу в Source, и он появляется, но не анимирует. Если я использую связывание, даже если ListBoxоно не для меня, оно вообще не подходит - это даст мне исключение, что мой путь к файлу неверный, даже если он тот же, который я использую, когда он появляется.
vapcguy

Обновление занимает много времени и должно обновляться каждый раз, когда оно появляется.
Йола

15

Это очень просто, если вы используете <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 для Source и найти путь в коде: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Обязательно установите для файла значение Build = Content и скопируйте его в выходной каталог.
Ман

Я использовал этот подход, потому что пакет NuGet WpfAnimatedGif не работал для меня - казалось, сбой, когда под большой нагрузкой процессора. Я установил gif на Build = Resource и установил Source, используя относительный путь из папки, в которой находилось окно, например, Source = "../../ Images / Rotating-e.gif". Работал хорошо для меня и не нужно сторонних DLL.
Ричард Мур

Это самое простое решение на сегодняшний день. Но проблема в том, что после того, как все кадры анимированного GIF просканированы, анимация останавливается. И нет никакого способа получить анимацию снова из кадра 0. Нет способа перезапустить анимацию или цикл навсегда. По крайней мере, я не нашел способа использовать <MediaElement />.
BoiseBaked

Кроме того, <MediaElement /> невероятно медленен и полон проблем с скачками потоков между его методами. Хмм ....
BoiseBaked

10

Вот моя версия управления анимированными изображениями. Вы можете использовать стандартное свойство 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
}

15
Этот код является частью одного из моих проектов. Я русский разработчик, который работает в России. Так что комментарии тоже на русском. Не каждый проект в мире - это «американо-английский» проект, Кори.
Майк Эшва

2
попытался использовать ваш код со следующей разметкой: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" /> но пока ничего не происходит
Sonic Soul

если я изменил его на использование JPEG, он показывает неподвижное изображение. просто не GIF. хороший код BTW
Sonic Soul

Блестящий, мне нужно было решение, где я мог, но GIF из словаря ресурсов -> BitmapImage -> анимированный GIF. Это оно!
mtbennett

9

Я использую эту библиотеку: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Сначала установите библиотеку в ваш проект (с помощью 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" />
        ...

Надеюсь поможет.

Источник: https://github.com/XamlAnimatedGif/WpfAnimatedGif


3
Это тот же (менее подробный) ответ, что и у @ IgorVaschuk от июня 2012 года, в настоящее время решение за второе место по голосованию.
Хит Кэрролл

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, это его масштабирование. Вы получите один артефакт (изображение), который не масштабируется должным образом.
Ман

5

Я изменил код Майка Эшвы и сделал так, чтобы он работал лучше. Вы можете использовать его с 1frame jpg png bmp или mutil-frame 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 не удалась во время выполнения.
Фарзан

Да, я попробовал это, и я получаю исключение. Это не работает с относительным Uris.
SuperJMN

3

У меня была эта проблема, пока я не обнаружил, что в 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>

1
Похоже, что недостатком этого подхода является то, что по умолчанию анимация продолжается даже после ее свертывания, что может привести к снижению производительности.
Линн

Это не DiscreteObjectKeyFrames, это DiscreteObjectKeyFrame. Единственное число.
до

@jairhumberto Я думаю, что это могло измениться между версиями. Это довольно старый (2011), но я действительно использовал этот точный код в проекте.
CodeMouse92

3

Спасибо за ваш пост, Джоэл, он помог мне решить проблему отсутствия поддержки WPF для анимированных GIF-файлов. Просто добавив немного кода, так как у меня было много времени с настройкой свойства pictureBoxLoading.Image из-за API Winforms.

Мне нужно было установить действие по созданию анимационного изображения GIF как «Содержимое», а каталог «Копировать в выходной файл» - «Копировать, если новее» или «всегда». Затем в MainWindow () я вызвал этот метод. Единственная проблема заключается в том, что когда я пытался избавиться от потока, он дал мне красный конверт вместо моего изображения. Я должен решить эту проблему. Это избавило от необходимости загружать BitmapImage и превращать его в растровое изображение (что, очевидно, убило мою анимацию, потому что это больше не 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, битмап, который использует поток, должен иметь поток, оставшийся живым в течение жизни битмапа. Обходной путь - заморозить или клонировать растровое изображение.
Джесси Чисхолм

1
Ему просто нужно сказать, чтобы установить .ImageLocationвместо .Image. У него был неправильный метод. .ImageLocationработает от корня проекта Visual Studio, так что у вас есть Imagesпапка, ваш путь будет imgBox.ImageLocation = "/Images/my.gif";. Если у вас есть папка с именем , Viewsгде у вас есть вид , который будет показывать изображение, чтобы получить обратно до Images, вы должны использовать 2 точки: imgBox.ImageLocation = "../Images/my.gif";.
vapcguy

1

Я пробовал весь путь выше, но у каждого есть свои недостатки, и благодаря всем вам я работаю над собственным 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, вы можете попробовать это.


Отличный образец. Необходимо обновить Initialize для проверки IsAutoStart, но в остальном работал как чемпион!
Стив Даннер

1
Явный вызов GC.Collect () оказывает ужасное влияние на производительность.
Кендзю

0

Ранее я сталкивался с подобной проблемой, мне нужно было воспроизвести .gifфайл в вашем проекте. У меня было два варианта:

  • используя PictureBox из WinForms

  • используя стороннюю библиотеку, такую ​​как WPFAnimatedGif от codeplex.com.

Версия с PictureBoxне работала у меня, и проект не мог использовать внешние библиотеки для этого. Таким образом, я сделал это для себя Bitmapс помощью ImageAnimator. Потому что стандарт 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файл из текущего каталога.


0

Небольшое улучшение 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;
    }

0

Я не уверен, что это было решено, но лучше всего использовать библиотеку WpfAnimatedGid . Это очень легко, просто и прямо в использовании. Это требует только 2 строки кода XAML и около 5 строк кода C # в коде позади.

Вы увидите все необходимые детали того, как это можно использовать там. Это то, что я также использовал вместо того, чтобы заново изобретать колесо


0

Добавляя к основному ответу, который рекомендует использовать 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);

0

Проверьте мой код, я надеюсь, что это помогло вам :)

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



                    }
                });
            }

0

Альтернатива ожидающей анимации в WPF:

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

Он покажет анимированный индикатор выполнения.


1
Вопрос не обязательно спрашивает об ожидающей анимации - это вопрос об анимированных GIF-файлах в целом. Очевидно, это может быть для ожидающей анимации, и в этом случае это может быть подходящей альтернативой. Но это может быть так же легко для любого количества других потребностей СМИ.
Джереми Кейни
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.