Wie kann ich ein animiertes GIF in WPF verwenden?


217

Was Steuerungstyp soll ich verwenden - Image, MediaElementusw.?


4
Hier ist eine aktuelle Zusammenfassung der folgenden Lösungen. Ich habe diese mit VS2015 implementiert. Die von Dario eingereichte GifImage-Klasse hat hervorragend funktioniert, aber einige meiner Gifs waren Artefakte. Der MediaElement-Ansatz von Pradip Daunde und nicael scheint im Vorschaubereich zu funktionieren, aber keines meiner Gifs wurde zur Laufzeit gerendert. Die WpfAnimatedGif-Lösung von IgorVaschuk und SaiyanGirl funktionierte problemlos, erforderte jedoch (offensichtlich) die Installation einer Drittanbieter-Bibliothek. Den Rest habe ich nicht ausprobiert.
Heath Carroll

Antworten:


214

Ich konnte nicht die beliebteste Antwort auf diese Frage (oben von Dario) bekommen, um richtig zu funktionieren. Das Ergebnis war eine seltsame, abgehackte Animation mit seltsamen Artefakten. Beste Lösung, die ich bisher gefunden habe: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Sie können es mit NuGet installieren

PM> Install-Package WpfAnimatedGif

und um es zu verwenden, in einem neuen Namespace im Fenster, in dem Sie das GIF-Bild hinzufügen und wie folgt verwenden möchten

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

Das Paket ist wirklich ordentlich, Sie können einige Attribute wie unten festlegen

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

und Sie können es auch in Ihrem Code verwenden:

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

BEARBEITEN: Silverlight-Unterstützung

Wenn Sie gemäß dem Kommentar von josh2112 Ihrem Silverlight-Projekt animierte GIF-Unterstützung hinzufügen möchten, verwenden Sie github.com/XamlAnimatedGif/XamlAnimatedGif


13
Dies funktionierte hervorragend und dauerte weniger als 60 Sekunden. Vielen Dank!
Ryan Sorensen

3
Viel bessere Antwort als jede der populären IMO, zumal es nicht darauf ankommt, dass Sie C # verwenden
Jamie E

8
Dies ist so viel besser als die akzeptierte Antwort: Verwendet die Metadaten des GIF, ist nicht abgehackt, ist ein NuGet-Paket, ist sprachunabhängig. Ich wünschte, Stackoverflow würde ein Misstrauensvotum für die akzeptierte Antwort ermöglichen.
John Gietzen

6
Ankündigung des öffentlichen Dienstes: Der Autor von WpfAnimatedGif hat sein Projekt als XamlAnimatedGif "neu gestartet" und unterstützt WPF, Windows Store (Win8), Windows 10 und Silverlight: github.com/XamlAnimatedGif/XamlAnimatedGif
josh2112

2
Was ist imghier
Amit Jha

104

Ich poste eine Lösung, die die Bildsteuerung erweitert und den Gif-Decoder verwendet. Der GIF-Decoder verfügt über eine Frames-Eigenschaft. Ich animiere das FrameIndexGrundstück. Das Ereignis ChangingFrameIndexändert die Quelleigenschaft in den Rahmen, der dem entspricht FrameIndex(dh im Decoder). Ich denke, dass das GIF 10 Bilder pro Sekunde hat.

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

Anwendungsbeispiel (XAML):

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

1
Dieser funktioniert und ist besser für XBAP-Apps geeignet, da Sie keine zusätzlichen Referenzen benötigen.
Max Galkin

1
Das ist cool. Durch Einfügen Ihres Konstruktorcodes in das Ereignis "Initialisiert" und Einführen einer Uri-Eigenschaft kann dieses Steuerelement auch in eine XAML-Datei eingefügt werden.
flq

1
+1, schön! Die tatsächliche Int32AnimationUsingKeyFrames
Thomas Levesque

7
Tatsächlich ist die Framerate für GIF konstant, sodass Sie schließlich keine Keyframes benötigen ... Sie können die Framerate mit lesen gf.Frames[0].MetaData.GetQuery("/grctlext/Delay")(gibt eine kurze Zeit zurück, die die Frame-Dauer in Hunderten von Sekunden darstellt)
Thomas Levesque

3
@vidstige, ja, ich erinnere mich nicht, warum ich diesen Kommentar damals (vor fast 2 Jahren) gemacht hatte. Ich bin mir bewusst, dass die Verzögerung für jeden Frame unterschiedlich sein kann, und meine WPF-animierte GIF- Bibliothek berücksichtigt dies ordnungsgemäß.
Thomas Levesque

38

Auch ich habe eine Suche durchgeführt und in nur einem Thread in den alten MSDN-Foren verschiedene Lösungen gefunden. (Link hat nicht mehr funktioniert, also habe ich ihn entfernt)

Die einfachste Ausführung scheint die Verwendung von WinForms zu sein PictureBox Steuerelement .

Fügen Sie zuerst einen Verweis auf System.Windows.Forms, WindowsFormsIntegrationund System.Drawingzu Ihrem Projekt hinzu.

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

Dann würden Sie im Window_LoadedHandler die pictureBoxLoading.ImageLocationEigenschaft auf den Bilddateipfad setzen, den Sie anzeigen möchten.

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

Das MediaElementSteuerelement wurde in diesem Thread erwähnt, es wird jedoch auch erwähnt, dass es sich um ein ziemlich schweres Steuerelement handelt. Daher gab es eine Reihe von Alternativen, darunter mindestens zwei selbstgebraute Steuerelemente, die auf dem Steuerelement basieren. ImageDies ist also das einfachste.


Können Sie dieses Hauptfenster mit AllowTransparency = "True" einfügen, wenn Sie WindowsFormsHost verwenden?
Junior Mayhé

@ Junior: Ja, du kannst einstellen AllowTransparency="True". Ob dies zu den gewünschten Ergebnissen führt oder nicht, ist eine andere Frage. Ich habe es selbst nicht versucht, aber ich würde wetten, dass das WindowsFormsHostüberhaupt nicht transparent wird. Der Rest der WindowMacht. Du musst es einfach versuchen, denke ich.
Joel B Fant

Ich hatte Probleme mit der pictureBoxLoading.Image aufgrund der Winform-API. Ich habe unten einen Code gepostet, der mein Problem gelöst hat. Danke für deine Lösung, Joel!
Sondlerd

Sieht so aus, als ob du tot bist. War es dieser Thread ?
Wischen

2
Beim Hinzufügen der Integrationsreferenz lautet der Name in meiner Benutzeroberfläche WindowsFormsIntegration ohne Punkt: i.imgur.com/efMiC23.png
yu yang Jian

36

Wie wäre es mit dieser winzigen App: Code dahinter:

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
Nett ! Funktionscode, der die Arbeit gut macht. Ich kann nicht glauben, dass es nicht mehr positive Stimmen gibt.
Wischen

2
Beste Antwort ... Sollte oben sein! Ich konnte es ohne Code zum <MediaElement LoadedBehavior="Play" Source="{Binding MyGifFile}" >Laufen bringen - genau das - Die MyGifFile ist nur der Dateiname (und der Pfad) meines animierten Gifs.
Anthony Nichols

Herrgott, warum sollte man sich überhaupt die Mühe machen, sich an die zu binden ListBoxoder überhaupt zu binden? Ich habe es ohne Bindung versucht, nur den Dateipfad in die Quelle eingefügt und es wird angezeigt, aber nicht animiert. Wenn ich die Bindung selbst mit der verwende ListBox, wird sie für mich überhaupt nicht angezeigt. Dies gibt mir die Ausnahme, dass mein Dateipfad falsch ist, obwohl er derselbe ist, für den ich ihn verwende, wenn er angezeigt wird.
Vapcguy

Die Aktualisierung dauert zu lange und muss jedes Mal aktualisiert werden, wenn sie angezeigt wird.
Yola

15

Es ist sehr einfach, wenn Sie verwenden <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" />

Nur für den Fall, dass Ihre Datei in Ihrer App gepackt ist, können Sie DataBinding für die Quelle verwenden und den Pfad im Code finden : public string SpinnerLogoPath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\images\mso_spinninglogo_blue_2.gif");. Stellen Sie sicher, dass die Datei auf Build = Content gesetzt und in das Ausgabeverzeichnis kopiert wird.
Der Muffin-Mann

Ich habe diesen Ansatz verwendet, weil das WpfAnimatedGif NuGet-Paket für mich nicht gut funktioniert hat - schien bei hoher CPU-Auslastung einen Fehler zu verursachen. Ich setze das GIF auf Build = Resource und setze die Quelle unter Verwendung eines relativen Pfads aus dem Ordner, in dem sich das Fenster befand, z. B. Source = "../../ Images / Rotating-z. B.". Funktionierte gut für mich und keine Notwendigkeit für DLLs von Drittanbietern.
Richard Moore

Dies ist bei weitem die einfachste Lösung. Das Problem dabei ist jedoch, dass die Animation stoppt, sobald alle Frames des animierten Gifs gescannt wurden. Und es gibt keine Möglichkeit, das GIF erneut aus Frame 0 zu animieren. Keine Möglichkeit, die Animation oder Schleife für immer neu zu starten. Zumindest habe ich mit <MediaElement /> keinen Weg gefunden.
BoiseBaked

Auch <MediaElement /> ist unglaublich langsam und voller Thread-Racing-Probleme zwischen seinen Methoden. Grrr….
BoiseBaked

10

Hier ist meine Version der animierten Bildsteuerung. Sie können die Standardeigenschaft Source zum Angeben der Bildquelle verwenden. Ich habe es weiter verbessert. Ich bin ein Russe, das Projekt ist russisch, daher sind die Kommentare auch auf Russisch. Aber trotzdem solltest du alles ohne Kommentare verstehen können. :) :)

/// <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
Dieser Code ist Teil eines meiner Projekte. Ich bin ein russischer Entwickler, der in Russland arbeitet. Kommentare sind also auch in russischer Sprache. Nicht jedes Projekt auf der Welt ist ein "amerikanisch-englisches" Projekt, Corey.
Mike Eshva

2
Ich habe versucht, Ihren Code mit dem folgenden Markup zu verwenden: <local: AnimatedImage Source = "/ Resources / ajax-loader.gif" />, aber bisher passiert nichts
Sonic Soul

Wenn ich es in ein JPEG ändere, zeigt es das Standbild. nur nicht das gif. netter Code BTW
Sonic Soul

Genial, ich brauchte eine Lösung, wo ich nur ein GIF aus dem Resource Dictionary -> BitmapImage -> animiertes GIF konnte. Das ist es!
Mtbennett

9

Ich benutze diese Bibliothek: https://github.com/XamlAnimatedGif/WpfAnimatedGif

Installieren Sie zunächst die Bibliothek in Ihrem Projekt (mithilfe der Package Manager-Konsole):

    PM > Install-Package WpfAnimatedGif

Verwenden Sie dann dieses Snippet in der XAML-Datei:

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

Ich hoffe es hilft.

Quelle: https://github.com/XamlAnimatedGif/WpfAnimatedGif


3
Dies ist die gleiche (weniger detaillierte) Antwort wie die von @ IgorVaschuk vom Juni 2012, derzeit die zweitplatzierte Lösung in Bezug auf die Stimmen.
Heath Carroll

5

Grundsätzlich die gleiche PictureBox-Lösung wie oben, diesmal jedoch mit dem Code-Behind, um eine eingebettete Ressource in Ihrem Projekt zu verwenden:

In XAML:

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

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

Gute Ergänzung. Rationalisiert es wirklich, soweit ich das beurteilen kann. (Das heißt, ich habe seit über drei Jahren nicht mehr in WPF geschrieben.)
CodeMouse92

Ich denke nicht, dass dies eine gute Idee ist, da einer der Hauptgründe, warum Sie sich für WPF entscheiden, die Skalierung der Anzeige ist. Am Ende erhalten Sie ein Artefakt (das Bild), das nicht richtig skaliert.
Der Muffin-Mann

5

Ich habe den Code von Mike Eshva geändert und dafür gesorgt, dass er besser funktioniert. Sie können ihn entweder mit 1frame jpg png bmp oder mutil-frame gif verwenden. Speicherstrom, den Sie an die Quelleigenschaft binden, die ein BitmapImage ist.

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

Dies ist ein benutzerdefiniertes Steuerelement. Sie müssen es in WPF App Project erstellen und die Vorlagenüberschreibung mit Stil löschen.


1
Ich musste UriSource nur so einstellen, dass es packt: // application: ,,, / Images / loader.gif. Das Festlegen von UriSource oder Source auf einen relativen Uri ist zur Laufzeit fehlgeschlagen.
Farzan

Ja, ich habe es versucht und bekomme eine Ausnahme. Es funktioniert nicht mit relativen Uris.
SuperJMN

3

Ich hatte dieses Problem, bis ich herausfand, dass Sie in WPF4 Ihre eigenen Keyframe-Bildanimationen simulieren können. Teilen Sie Ihre Animation zunächst in eine Reihe von Bildern auf und benennen Sie sie mit "Image1.gif", "Image2, gif" usw. Importieren Sie diese Bilder in Ihre Lösungsressourcen. Ich gehe davon aus, dass Sie sie am Standardspeicherort für Bilder ablegen.

Sie werden das Bildsteuerelement verwenden. Verwenden Sie den folgenden XAML-Code. Ich habe das Unwesentliche entfernt.

<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
Es scheint, dass ein Nachteil dieses Ansatzes darin besteht, dass die Animation standardmäßig auch nach dem Reduzieren fortgesetzt wird, was zu einem Leistungseinbruch führen kann.
Lynn

Es ist nicht DiscreteObjectKeyFrames, es ist DiscreteObjectKeyFrame. Singular.
Jairhumberto

@jairhumberto Ich denke, das hat sich zwischen den Versionen geändert. Dies ist ziemlich alt (2011), aber ich habe tatsächlich genau diesen Code in einem Projekt verwendet.
CodeMouse92

3

Vielen Dank für Ihren Beitrag Joel, es hat mir geholfen, die fehlende Unterstützung von WPF für animierte GIFs zu beheben. Ich habe nur ein wenig Code hinzugefügt, da ich verdammt viel Zeit mit dem Festlegen der pictureBoxLoading.Image-Eigenschaft aufgrund der Winforms-API hatte.

Ich musste die Build-Aktion meines animierten GIF-Bildes auf "Inhalt" und das Verzeichnis "In Ausgabeverzeichnis kopieren" auf "Kopieren, wenn neuer" oder "immer" setzen. Dann habe ich im MainWindow () diese Methode aufgerufen. Das einzige Problem ist, dass ich beim Versuch, den Stream zu entsorgen, anstelle meines Bildes eine rote Umschlaggrafik erhielt. Ich muss das Problem lösen. Dies beseitigte den Schmerz, ein BitmapImage zu laden und in eine Bitmap zu ändern (was meine Animation offensichtlich tötete, weil es kein GIF mehr ist).

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

Betreff: Als ich versuchte, den Stream zu entsorgen Laut MSDN muss bei einer Bitmap, die einen Stream verwendet, der Stream für die gesamte Lebensdauer der Bitmap am Leben bleiben. Die Problemumgehung besteht darin, die Bitmap entweder einzufrieren oder zu klonen.
Jesse Chisholm

1
Er musste nur sagen, .ImageLocationstatt zu setzen .Image. Er hatte die falsche Methode. .ImageLocationfunktioniert im Stammverzeichnis des Visual Studio-Projekts. Angenommen, Sie haben einen ImagesOrdner, dann lautet Ihr Pfad imgBox.ImageLocation = "/Images/my.gif";. Wenn Sie einen Ordner mit dem Namen haben, Viewsin dem Sie eine Ansicht haben, in der das Bild Imagesangezeigt wird, müssen Sie zwei Punkte verwenden : imgBox.ImageLocation = "../Images/my.gif";.
Vapcguy

1

Ich habe den ganzen Weg oben versucht, aber jeder hat seine Kürze, und dank euch allen arbeite ich mein eigenes GifImage aus:

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

Verwendung:

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

Da dies keinen Speicherverlust verursachen und die eigene Zeitleiste des GIF-Bilds animieren würde, können Sie es versuchen.


Hervorragende Probe. Needs Initialize aktualisiert, um zu prüfen IsAutoStart, aber ansonsten hat es wie ein Champion funktioniert!
Steve Danner

1
Das explizite Aufrufen von GC.Collect () hat schreckliche Auswirkungen auf die Leistung.
Kędrzu

0

Zuvor hatte ich ein ähnliches Problem: Ich musste eine .gifDatei in Ihrem Projekt abspielen . Ich hatte zwei Möglichkeiten:

  • mit PictureBox von WinForms

  • Verwenden einer Bibliothek eines Drittanbieters wie WPFAnimatedGif von codeplex.com.

Version mit PictureBoxhat bei mir nicht funktioniert und das Projekt konnte keine externen Bibliotheken dafür verwenden. Also habe ich es Bitmapmit Hilfe für mich selbst geschafft ImageAnimator. Weil Standard BitmapImagedie Wiedergabe von nicht unterstützt.gif Dateien .

Vollständiges Beispiel:

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

Bitmapunterstützt keine URI- Direktive, daher lade ich eine .gifDatei aus dem aktuellen Verzeichnis.


0

Kleine Verbesserung der GifImage.Initialize()Methode, die das richtige Frame-Timing aus GIF-Metadaten liest.

    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

Ich bin nicht sicher, ob dies behoben wurde, aber der beste Weg ist, die WpfAnimatedGid-Bibliothek zu verwenden . Es ist sehr einfach, unkompliziert und unkompliziert zu bedienen. Es sind nur 2 Zeilen XAML-Code und etwa 5 Zeilen C # -Code im Code dahinter erforderlich.

Sie sehen alle notwendigen Details, wie dies dort verwendet werden kann. Das habe ich auch benutzt, anstatt das Rad neu zu erfinden


0

Wenn Sie der Hauptantwort hinzufügen, die die Verwendung von WpfAnimatedGif empfiehlt , müssen Sie am Ende die folgenden Zeilen hinzufügen, wenn Sie ein Bild mit einem Gif austauschen, um sicherzustellen, dass die Animation tatsächlich ausgeführt wird:

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

Ihr Code sieht also folgendermaßen aus:

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

Überprüfen Sie meinen Code, ich hoffe das hat Ihnen geholfen :)

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

                            }
                        });
                    }

oder

     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

Eine Alternative zur Warteanimation in WPF ist:

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

Es wird ein animierter Fortschrittsbalken angezeigt.


1
Die Frage bezieht sich nicht unbedingt auf eine wartende Animation, sondern auf animierte GIFs im Allgemeinen. Offensichtlich , dass könnte für eine Warte Animation sein, wobei in diesem Fall könnte dies eine geeignete Alternative sein. Aber es könnte genauso gut für eine beliebige Anzahl anderer Medienbedürfnisse sein.
Jeremy Caney
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.