Як змусити анімований gif працювати у WPF?


218

Який тип контролю слід використовувати - Image, MediaElementі т.д.?


4
Ось нещодавній підсумок наведених нижче рішень. Я реалізував їх за допомогою VS2015. Клас GifImage, поданий Даріо, працював чудово, але деякі мої гіфки були артефактовані. Підхід MediaElement Прадіпа Даунде і Нікаеля, здається, працює в області попереднього перегляду, але жоден з моїх 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);

EDIT: Підтримка Silverlight

Відповідно до коментаря josh2112, якщо ви хочете додати анімовану підтримку GIF до свого проекту Silverlight, тоді використовуйте github.com/XamlAnimatedGif/XamlAnimatedGif


13
Це спрацювало чудово і знадобилося менше 60 секунд на реалізацію. Дякую!
Райан Соренсен

3
Шлях краще відповісти, ніж будь-який із популярних IMO, тим більше, що він не покладається на вас, використовуючи C #
Jamie E

8
Це набагато краще, ніж прийнята відповідь: використовує метадані gif, не є чіпкими, є пакетом NuGet, є мовним агностиком. Я хотів би, щоб стак-потік був дозволений для вотуму недовіри прийнятій відповіді.
Джон Гітцен

6
Оголошення громадських служб: Автор WpfAnimatedGif "перезавантажив" свій проект як XamlAnimatedGif, і він підтримує WPF, Windows Store (Win8), Windows 10 та Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112,

2
Що imgтут?
amit jha

104

Я розміщую рішення, яке розширює контроль зображення та використовує декодер Gif. 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
Круто. Помістивши свій конструкторський код у події "Ініціалізований" та ввівши властивість Uri, цей елемент управління також може бути розміщений у файлі XAML.
flq

1
+1, приємний! Однак він не враховує фактичну тривалість кадру зображення ... Якщо ви можете знайти спосіб прочитати цю інформацію, ви можете змінити код на використанняInt32AnimationUsingKeyFrames
Thomas Levesque

7
Насправді частота кадрів є постійною для GIF, тому вам не потрібні ключові кадри. Зрештою, ви можете прочитати кадр gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(повертає ушорт, тривалість кадру в сотні секунд)
Томас Левеск

3
@vidstige, так, я не пам'ятаю, чому я робив цей коментар у той час (майже 2 роки тому). Я знаю, що затримка може бути різною для кожного кадру, і моя бібліотека анімованих GP-файлів WPF належним чином враховує це.
Томас Левеск

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?
Молодший Мейхе

@Junior: Так, ви можете встановити AllowTransparency="True". Незалежно від того, чи це призведе до результатів, які ви маєте на увазі - інша справа. Я сам не пробував цього, але ставлюся, що це WindowsFormsHostзовсім не стане прозорим. Решта Windowсили. Вам просто доведеться спробувати, я думаю.
Joel B Fant

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

Схоже, ваш подібний мертвий. Це була ця нитка ?
витер

2
Додаючи посилання на інтеграцію, його ім'я в моєму інтерфейсі - це WindowsFormsIntegration, без крапки: i.imgur.com/efMiC23.png
yu

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
Приємно! Короткий код, виконуючи роботу добре. Не можу повірити, що в ньому немає більшої підтримки.
витер

2
Найкраща відповідь ... Повинен бути вгорі! Мені вдалося змусити його працювати без будь-якого коду позаду - саме це <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >- MyGifFile - це лише назва файлу (і шлях) мого анімованого gif.
Ентоні Ніколс

Боже, чому взагалі не турбуєшся прив’язуватися до цього ListBoxчи взагалі пов'язувати? Я спробував це без прив'язки, просто поклав шлях до файлу до Джерела, і він з'явиться, але не анімує. Якщо я використовую прив'язку, навіть із тим 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");. Обов’язково встановіть файл на Збірка = Зміст та скопіюйте у вихідний каталог.
Людина-булочка

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

Це найпростіше рішення на сьогоднішній день. Але проблема з цим полягає в тому, що як тільки всі кадри анімаційного gif проскануються, анімація припиняється. І немає жодного способу змусити gif анімувати з кадру 0 знову. Немає можливості назавжди перезапустити анімацію чи цикл. Принаймні, я не знайшов способу використання <MediaElement />.
BoiseBaked

Крім того, <MediaElement /> неймовірно повільний і повний проблем з перебігом потоку між його методами. Grrr….
BoiseBaked

10

Ось моя версія управління анімованими зображеннями. Ви можете використовувати стандартне джерело властивості для вказівки джерела зображення. Я його ще більше вдосконалив. Я росіянин, проект - російський, тому коментарі також є російською. Але все одно ви повинні мати можливість зрозуміти все без коментарів. :)

/// <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. nice code BTW
Sonic Soul

Блискуче, мені потрібно було рішення, де я міг би не GIF зі словника ресурсів -> BitmapImage -> анімований GIF. Це воно!
mtbennett

9

Я використовую цю бібліотеку: 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


3
Це та сама (менш детальна) відповідь, як і відповідь @ Ігоря Ващука з червня 2012 року, на даний момент рішення, яке займає 2 місце, є голосовим.
Хіт Керролл

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

5

Я змінив код Майка Ешва, і змусив його працювати краще. Ви можете використовувати його з 1 кадром jpg png bmp або з mutil-frame gif. Якщо ви хочете прив’язати урі до управління, прив’яжіть властивості UriSource або ви хочете прив’язати будь-який потік пам'яті, який ви прив'язуєте до власного джерела, що є 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 і видалити шаблону, що перекриває стиль.


1
Мені просто довелося встановити UriSource для упаковки: // application: ,,, / Images / loader.gif. Встановлення UriSource або Source для відносного Uri під час виконання не вдалося.
Farzan

Так, я спробував це, і я отримую виняток. Він не працює при відносному сечовипусканні.
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. Однини.
jairhumberдо

@jairhumberto Я думаю, що це могло змінитися між версіями. Це досить старий (2011 р.), Але я справді використовував саме цей код у проекті.
CodeMouse92

3

Дякую за ваш пост Джоель, він допоміг мені вирішити відсутність підтримки WPF для анімованих GIF. Просто додавши трохи коду, оскільки у мене був чорт часу із встановленням властивості pictureBoxLoading.Image завдяки api Winforms.

Мені довелося встановити анімоване зображення gif-зображення як "Вміст", а каталог "Копіювати на вихід" на "Копіювати, якщо новіше" або "Завжди". Потім у MainWindow () я назвав цей метод. Єдине питання полягає в тому, що коли я намагався розпоряджатися потоком, він давав мені червоний графічний конверт замість мого зображення. Мені доведеться вирішити цю проблему. Це зняло біль завантаження BitmapImage та зміни його на Bitmap (що, очевидно, вбило мою анімацію, оскільки вона більше не є gif).

private void SetupProgressIcon()
{
   Uri uri = new Uri("pack://application:,,,/WPFTest;component/Images/animated_progress_apple.gif");
   if (uri != null)
   {
      Stream stream = Application.GetContentStream(uri).Stream;   
      imgProgressBox.Image = new System.Drawing.Bitmap(stream);
   }
}

re: коли я намагався розпоряджатися потоком Відповідно до MSDN, у Bitmap, що використовує Stream, повинно бути потоком залишатися живим протягом життя Bitmap. Робота полягає в тому, щоб заморозити або клонувати растрову карту.
Джессі Чизгольм

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

Оскільки це не спричинило б витік пам'яті, а воно оживило графічний графік власним часовим рядком, ви можете спробувати.


Відмінний зразок. Потреби ініціалізуються оновлені, щоб перевірити IsAutoStart, але в іншому випадку працювали як чемпіон!
Стів Даннер

1
Явне виклик GC.Collect () має жахливий вплив на продуктивність.
Kędrzu

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.