Jak uzyskać animowany gif do pracy w WPF?


218

Jaki rodzaj sterowania należy używać - Image, MediaElementitp?


4
Oto najnowsze podsumowanie poniższych rozwiązań. Zaimplementowałem je za pomocą VS2015. Klasa GifImage przesłana przez Dario działała świetnie, ale niektóre z moich gifów zostały artefaktowane. Podejście MediaElement autorstwa Pradipa Daunde i nicaela wydaje się działać w obszarze podglądu, ale żaden z moich gifów nie był renderowany podczas uruchamiania. Rozwiązanie WpfAnimatedGif autorstwa IgorVaschuk i SaiyanGirl działało świetnie bez problemów, ale wymagało instalacji biblioteki innej firmy (oczywiście). Nie wypróbowałem reszty.
Heath Carroll

Odpowiedzi:


214

Nie mogłem uzyskać najpopularniejszej odpowiedzi na to pytanie (powyżej autorstwa Dario), aby działać poprawnie. Rezultatem była dziwna, niespokojna animacja z dziwnymi artefaktami. Najlepsze rozwiązanie, jakie do tej pory znalazłem: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Możesz zainstalować go za pomocą NuGet

PM> Install-Package WpfAnimatedGif

i użyć go, w nowej przestrzeni nazw w oknie, w którym chcesz dodać obraz gif i użyć go jak poniżej

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

Pakiet jest naprawdę schludny, możesz ustawić niektóre atrybuty, takie jak poniżej

<Image gif:ImageBehavior.RepeatBehavior="3x"
       gif:ImageBehavior.AnimatedSource="Images/animated.gif" />

i możesz go również użyć w kodzie:

var image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(fileName);
image.EndInit();
ImageBehavior.SetAnimatedSource(img, image);

EDYCJA: Wsparcie Silverlight

Zgodnie z komentarzem josh2112, jeśli chcesz dodać obsługę animowanego GIF do projektu Silverlight, użyj github.com/XamlAnimatedGif/XamlAnimatedGif


13
Udało się to doskonale, a wdrożenie zajęło mniej niż 60 sekund. Dzięki!
Ryan Sorensen

3
O wiele lepsza odpowiedź niż jakikolwiek z popularnych IMO, zwłaszcza że nie polega na tobie przy użyciu C #
Jamie E

8
Jest to o wiele lepsze niż zaakceptowana odpowiedź: używa metadanych gif, nie jest niepewny, jest pakietem NuGet, jest niezależny od języka. Chciałbym, aby przepełnienie stosu pozwoliło na głosowanie bez zaufania w zaakceptowanej odpowiedzi.
John Gietzen

6
Ogłoszenie o usługach publicznych: autor WpfAnimatedGif „zrestartował” swój projekt jako XamlAnimatedGif i obsługuje WPF, Windows Store (Win8), Windows 10 i Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Co imgtu jest
amit jha,

104

Zamieszczam rozwiązanie rozszerzające kontrolę obrazu i korzystające z dekodera Gif. Dekoder gif ma właściwość frames. Animuję FrameIndexnieruchomość. Zdarzenie ChangingFrameIndexzmienia właściwość source na ramkę odpowiadającą FrameIndex(czyli w dekoderze). Myślę, że gif ma 10 klatek na sekundę.

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);
    }
}

Przykład użycia (XAML):

<controls:GifImage x:Name="gifImage" Stretch="None" GifSource="/SomeImage.gif" AutoStart="True" />

1
Ten działa i lepiej dla aplikacji XBAP, ponieważ nie potrzebujesz dodatkowych odniesień.
Max Galkin,

1
To super. Umieszczając kod konstruktora w zdarzeniu „Initialized” i wprowadzając właściwość Uri, formant ten można również umieścić w pliku XAML.
flq

1
+1, fajny! Jednak nie bierze pod uwagę faktycznego czasu trwania ramki obrazu ... Jeśli możesz znaleźć sposób na odczytanie tych informacji, możesz zmienić kod, aby użyćInt32AnimationUsingKeyFrames
Thomas Levesque

7
W rzeczywistości liczba klatek na sekundę jest stała dla formatu GIF, więc nie potrzebujesz klatek kluczowych w końcu ... Możesz odczytać gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")liczbę klatek na sekundę za pomocą (zwraca ushort, który jest czasem trwania ramki w setkach sekund)
Thomas Levesque

3
@vidstige, tak, nie pamiętam, dlaczego napisałem ten komentarz w tym czasie (prawie 2 lata temu). Zdaję sobie sprawę, że opóźnienie może być różne dla każdej klatki, a moja biblioteka animacji GIF WPF właściwie to uwzględnia.
Thomas Levesque

38

Ja również szukałem i znalazłem kilka różnych rozwiązań w jednym wątku na starych forach MSDN. (link już nie działał, więc go usunąłem)

Najprostszym do wykonania wydaje się użycie WinForm PictureBox formantu i poszło tak (zmieniło kilka rzeczy z wątku, w większości takie same).

Dodaj odwołanie do System.Windows.Forms, WindowsFormsIntegrationi System.Drawingdo swojego projektu.

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

Następnie w Window_Loadedmodule obsługi ustawisz pictureBoxLoading.ImageLocationwłaściwość na ścieżkę pliku obrazu, którą chcesz wyświetlić.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    pictureBoxLoading.ImageLocation = "../Images/mygif.gif";
}

MediaElementKontrola została wymieniona w tym wątku, ale jest również wspomnieć, że jest to dość ciężki kontrola, więc było wiele alternatyw, w tym co najmniej 2 homebrewed kontroli na podstawie Imagekontroli, więc jest to najprostsze.


czy możesz umieścić to okno główne za pomocą AllowTransparency = "True" podczas korzystania z WindowsFormsHost?
Junior Mayhé,

@ Junior: Tak, możesz ustawić AllowTransparency="True". To, czy to przyniesie wyniki, o których myślisz, to inna sprawa. Sam tego nie próbowałem, ale założę się, że w WindowsFormsHostogóle nie stałoby się to przejrzyste. Reszta Windowpotęgi. Myślę, że po prostu będziesz musiał spróbować.
Joel B Fant,

Miałem problem z pictureBoxLoading.Image z powodu interfejsu API winform. Poniżej zamieściłem kod, który rozwiązał mój problem. Dzięki za twoje rozwiązanie, Joel!
sondlerd

Wygląda na to, że twoja osoba nie żyje. Czy to był ten wątek ?
wycierać

2
Podczas dodawania odwołania integracji jego nazwa w moim interfejsie użytkownika to WindowsFormsIntegration, bez kropki: i.imgur.com/efMiC23.png
yu yang Jian

36

Co powiesz na tę małą aplikację: Kod za:

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
Miły ! Krótki kod, dobrze wykonujący pracę. Nie mogę uwierzyć, że nie ma więcej pozytywnych opinii.
wip

2
Najlepsza odpowiedź ... Powinien być na górze! Byłem w stanie sprawić, że działał bez żadnego kodu z tyłu - tylko to <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >- MyGifFile to tylko nazwa pliku (i ścieżka) mojego animowanego gifa.
Anthony Nichols

Jezu, po co w ogóle zawracać sobie głowę wiązaniem się ListBoxlub wiązaniem w ogóle? Próbowałem bez wiązania, po prostu wstaw ścieżkę do pliku źródłowego i pojawi się, ale nie będzie animowana. Jeśli użyję wiązania, nawet z ListBox, w ogóle nie wyjdzie, dla mnie - da mi wyjątek, że moja ścieżka do pliku jest niepoprawna, nawet jeśli jest to ta sama, której używam, kiedy się pojawia.
vapcguy

Aktualizacja trwa długo i musi być aktualizowana za każdym razem, gdy się pojawi.
Yola

15

To bardzo proste, jeśli używasz <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" />

Tylko w przypadku, gdy plik jest spakowany w aplikacji można użyć DataBinding do źródła i wybrać ścieżkę w kodzie: public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Pamiętaj, aby ustawić plik na Build = Content i skopiować do katalogu wyjściowego.
The Muffin Man,

Zastosowałem to podejście, ponieważ pakiet WpfAnimatedGif NuGet nie działał dla mnie dobrze - wydawał się zlewać, gdy byłby obciążony procesorem. Ustawiłem gif na Build = Resource i ustawiłem Source, używając ścieżki względnej z folderu, w którym znajdowało się okno, np. Source = "../../ Images / Rotating-egif". Działa dobrze dla mnie i nie potrzeba zewnętrznych bibliotek DLL.
Richard Moore,

To zdecydowanie najprostsze rozwiązanie. Problem polega jednak na tym, że po zeskanowaniu wszystkich klatek animowanego gif animacja się zatrzymuje. I nie ma sposobu, aby ponownie animować gif z klatki 0. Nie ma możliwości ponownego uruchomienia animacji lub zapętlenia na zawsze. Przynajmniej nie znalazłem sposobu, używając <MediaElement />.
BoiseBaked

Również <MediaElement /> jest niewiarygodnie wolny i pełen problemów z wyścigami między metodami. Grrr….
BoiseBaked

10

Oto moja wersja kontroli animowanego obrazu. Do określenia źródła obrazu można użyć standardowej właściwości Źródło. Udoskonaliłem to. Jestem Rosjaninem, projekt jest rosyjski, więc komentarze są również w języku rosyjskim. Ale i tak powinieneś być w stanie zrozumieć wszystko bez komentarzy. :)

/// <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
Ten kod jest częścią jednego z moich projektów. Jestem rosyjskim programistą, który pracuje w Rosji. Tak więc komentarze są również w języku rosyjskim. Nie każdy projekt na świecie jest projektem „amerykańsko-angielskim”, Corey.
Mike Eshva,

2
próbowałem użyć kodu z następującym znacznikiem: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" />, ale jak dotąd nic się nie dzieje
Sonic Soul

jeśli zmienię go na JPEG, wyświetli się nieruchomy obraz. po prostu nie gif. ładny kod BTW
Sonic Soul,

Genialnie, potrzebowałem rozwiązania, w którym mógłbym tylko GIF ze słownika zasobów -> BitmapImage -> animowany GIF. To jest to!
mtbennett

9

Korzystam z tej biblioteki: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Najpierw zainstaluj bibliotekę w swoim projekcie (używając konsoli Menedżera pakietów):

    PM > Install-Package WpfAnimatedGif

Następnie użyj tego fragmentu kodu do pliku 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" />
        ...

Mam nadzieję, że pomoże.

Źródło: https://github.com/XamlAnimatedGif/WpfAnimatedGif


3
To ta sama (mniej szczegółowa) odpowiedź, jak @ IgorVaschuk z czerwca 2012 r., Obecnie drugie miejsce pod względem głosów.
Heath Carroll

5

Zasadniczo to samo rozwiązanie PictureBox powyżej, ale tym razem z kodem do użycia Embedded Resource w projekcie:

W XAML:

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

W Code-Behind:

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

Dobry dodatek Naprawdę usprawnia to, z tego co mogę powiedzieć. (To powiedziawszy, nie pisałem w WPF od ponad trzech lat.)
CodeMouse92

Nie sądzę, żeby to był dobry pomysł, ponieważ jednym z głównych powodów, dla których korzystasz z WPF, jest skalowanie wyświetlania. Otrzymasz jeden artefakt (obraz), który nie skaluje się poprawnie.
The Muffin Man,

5

Zmodyfikowałem kod Mike'a Eshvy i sprawiłem, że działa lepiej. Możesz go używać z 1frame jpg png bmp lub mutil-frame gif. Jeśli chcesz powiązać uri z kontrolką, powiąż właściwości UriSource lub jeśli chcesz powiązać dowolne strumień pamięci, który wiąże właściwość Source, którą jest 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
}

To jest kontrola niestandardowa. Musisz go utworzyć w WPF App Project i usunąć stylowe zastąpienie szablonu.


1
Po prostu musiałem ustawić UriSource do spakowania: // application: ,,, / Images / loader.gif. Ustawienie UriSource lub Source na względne Uri nie powiodło się w czasie wykonywania.
Farzan

Tak, próbowałem i dostaję wyjątek. Nie działa z względnym moczem.
SuperJMN

3

Miałem ten problem, dopóki nie odkryłem, że w WPF4 możesz symulować własne animacje klatek kluczowych. Najpierw podziel animację na serię obrazów, nadaj im tytuł „Image1.gif”, „Image2, gif” i tak dalej. Zaimportuj te obrazy do zasobów rozwiązania. Zakładam, że umieściłeś je w domyślnej lokalizacji zasobów dla obrazów.

Będziesz używać kontroli obrazu. Użyj następującego kodu XAML. Usunąłem to, co nieistotne.

<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
Wydaje się, że jedną wadą tego podejścia jest to, że domyślnie animacja jest kontynuowana nawet po jej zwinięciu, co może spowodować spadek wydajności.
Lynn,

To nie jest DiscreteObjectKeyFrames, to DiscreteObjectKeyFrame. Pojedynczy.
jairhumberto

@jairhumberto Myślę, że mogło się to zmienić między wersjami. To jest dość stare (2011), ale rzeczywiście użyłem tego dokładnego kodu w projekcie.
CodeMouse92

3

Dzięki za twój post Joel, pomogło mi to rozwiązać brak wsparcia WPF dla animowanych GIF-ów. Właśnie dodałem trochę kodu, ponieważ miałem trochę czasu z ustawieniem właściwości pictureBoxLoading.Image ze względu na interfejs API Winforms.

Musiałem ustawić akcję kompilacji mojego animowanego obrazu gif jako „Treść”, a katalog Kopiuj do pliku wyjściowego na „Kopiuj, jeśli nowszy” lub „zawsze”. Następnie w MainWindow () wywołałem tę metodę. Jedynym problemem jest to, że kiedy próbowałem pozbyć się strumienia, dało mi to czerwoną grafikę koperty zamiast mojego obrazu. Będę musiał rozwiązać ten problem. To usunęło ból związany z ładowaniem BitmapImage i zamianą go w Bitmapę (co oczywiście zabiło moją animację, ponieważ nie jest już gifem).

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);
   }
}

Odp .: Kiedy próbowałem usunąć strumień Zgodnie z MSDN, Bitmapa korzystająca ze Strumienia musi mieć strumień pozostający przy życiu przez cały okres obowiązywania Bitmapy. Obejściem problemu jest zablokowanie lub sklonowanie mapy bitowej.
Jesse Chisholm,

1
On po prostu potrzebne do powiedzenia zestawu .ImageLocationzamiast .Image. On miał niewłaściwą metodę. .ImageLocationdziała z katalogu głównego projektu Visual Studio, więc powiedzmy, że masz Imagesfolder, twoja ścieżka jest wtedy imgBox.ImageLocation = "/Images/my.gif";. Jeśli masz folder o nazwie Viewsgdzie masz widok, który pokaże obraz, aby wrócić do Images, trzeba by użyć 2 punkty: imgBox.ImageLocation = "../Images/my.gif";.
vapcguy

1

Próbowałem już wyżej, ale każdy ma swoją krótkość, a dzięki Wam wypracowuję własny 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);
            }
    }
}

Stosowanie:

<localComponents:GifImage x:Name="gifImage" IsAutoStart="True" GifSource="{Binding Path=value}" />

Ponieważ nie spowoduje to wycieku pamięci i animuje własną linię czasu obrazu gif, możesz spróbować.


Doskonała próbka. Potrzebuje zainicjować zaktualizowane, aby sprawdzić IsAutoStart, ale w przeciwnym razie działało jak mistrz!
Steve Danner

1
Wyraźne wywołanie GC.Collect () ma straszny wpływ na wydajność.
Kędrzu

0

Wcześniej miałem podobny problem, musiałem odtworzyć .gifplik w twoim projekcie. Miałem dwie możliwości:

  • przy użyciu PictureBox z WinForms

  • za pomocą biblioteki innej firmy, takiej jak WPFAnimatedGif z codeplex.com.

Wersja z PictureBoxnie działała dla mnie, a projekt nie mógł dla niej używać bibliotek zewnętrznych. Więc udało mi się to Bitmapz pomocą ImageAnimator. Ponieważ standard BitmapImagenie obsługuje odtwarzania .gifplików.

Pełny przykład:

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));
    }
}

Bitmapnie obsługuje dyrektywy URI , więc ładuję .gifplik z bieżącego katalogu.


0

Małe ulepszenie GifImage.Initialize()metody, która odczytuje właściwe taktowanie ramki z metadanych 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

Nie jestem pewien, czy problem został rozwiązany, ale najlepszym sposobem jest użycie biblioteki WpfAnimatedGid . Jest bardzo łatwy, prosty i prosty w użyciu. Wymaga tylko 2 linii kodu XAML i około 5 linii kodu C # w kodzie z tyłu.

Zobaczysz wszystkie niezbędne szczegóły, w jaki sposób można go tam wykorzystać. Tego właśnie użyłem zamiast ponownie wynaleźć koło


0

Dodając do głównej odpowiedzi, która zaleca użycie WpfAnimatedGif , musisz dodać następujące linie na końcu, jeśli zamieniasz obraz za pomocą Gif, aby upewnić się, że animacja faktycznie się wykonuje:

ImageBehavior.SetRepeatBehavior(img, new RepeatBehavior(0));
ImageBehavior.SetRepeatBehavior(img, RepeatBehavior.Forever);

Twój kod będzie wyglądał następująco:

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

Sprawdź mój kod, mam nadzieję, że to ci pomogło :)

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

                            }
                        });
                    }

lub

     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

Alternatywą dla animacji oczekiwania w WPF jest:

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

Wyświetli animowany pasek postępu.


1
Pytanie niekoniecznie dotyczy pytania o animację oczekiwania - chodzi o animowane pliki GIF w ogóle. Oczywiście, że może to być animacja oczekiwania, w którym to przypadku może to być odpowiednia alternatywa. Ale może to być równie łatwe dla dowolnej liczby innych potrzeb medialnych.
Jeremy Caney
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.