In WPF, the Image control is a lightweight control that always display the first frame of the image file. If you are displaying a GIF file with multiple frames, you will notice that Image control only display the first image frame of that file.
This is because WPF drives this control to be more specific and let the user do its own technique how to do the animation. Also, this is the reason why WPF developers introduced a thing/classes of type Animation. The most common class that address this issue is the Int32Animation class of System.Windows.Media.Animation namespace.
Below are our explanation how to animate GIF file in WPF Image control.
First you need to create a UserControl that inherits the Image (from System.Windows.Controls) namespace. See the codes below.
In XAML
<Image x:Class="CodesDirectory.Controls.AnimatableImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
Stretch="Fill">
</Image>
namespace CodesDirectory.Controls
{
public partial class AnimatableImage
: Image
{
public AnimatableImage()
{
InitializeComponent();
}
}
}
In order for you to determine what frame are you going to display in a span of time, you need to get the frame counts and the Image object per frame. So all you need to do is a variable/property that will handle the frame count values.
public int FrameCount { get; private set; }
private bool IsAnimating { get; private set; }
private bool
IsFramesInitiated { get; set; }
You will be using FrameCount property to handle the value of how many frames are there in the image, you will be using IsFramesInitiated property to handle a value whether the frames already initiated and you will be using IsAnimating property to handle a value whether the current image is animating.
The codes below will address how to get the frames and its length.
private void InitializeImageFrames()
{
BitmapFrame
bf = this.Source as BitmapFrame;
if (object.ReferenceEquals(bf,
null)) bf = BitmapFrame.Create((BitmapSource)this.Source);
if (!object.ReferenceEquals(bf.Decoder,
null))
{
this.FrameCount = bf.Decoder.Frames.Count;
this.IsFramesInitiated = true;
}
}
With the help of BitmapFrame (of System.Windows.Media.Imaging) object, we can drill down how many frame the image object has.
After that, you need to declare a new DependencyProperty that implements a PropertyMetaData to listen the events made on every changed. Below is the code on how to do it.
private static readonly DependencyProperty CurrentFrameImageProperty =
DependencyProperty.Register("CurrentFrameImage",
typeof(int),
typeof(Image),
new PropertyMetadata(0,
OnCurrentFrameImageChanged));
private static void OnCurrentFrameImageChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
{
Image animatedImage = (Image)dpo;
animatedImage.Source = ((BitmapFrame)animatedImage.Source).Decoder.Frames[(int)e.NewValue];
}
Also, you need to provide a facility to check whether the Source property has been changed. In this case, you need to override the OnPropertyChanged event and check whether the property Source has been changed. To do this, please see codes below.
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs
e)
{
if (object.Equals(e.Property,
Image.SourceProperty))
{
this.InitializeImageFrames();
base.OnPropertyChanged(e);
}
}
To be more control like implementation, you should create two additional method to manage the animation effect of the control. Let's call them StartAnimate and StopAnimation.
StartAnimate Method
For StartAnimate method, please see the implementation below.
public void StartAnimate()
{
if (!this.IsAnimating
&& this.IsFramesInitiated)
{
Int32Animation animation = new Int32Animation(0, this.FrameCount - 1,
new Duration(TimeSpan.FromMilliseconds(AnimatableImage.TIME_MILLISECONDS_PER_IMAGEFRAME
* this.FrameCount)));
animation.RepeatBehavior = RepeatBehavior.Forever;
this.BeginAnimation(CurrentFrameImageProperty,
animation, HandoffBehavior.SnapshotAndReplace);
this.IsAnimating = true;
}
}
There, calling the BeginAnimation will do the job. You will notice there is a constant value named TIME_MILLISECONDS_PER_IMAGEFRAME. You can declare this at the top of your class as a constant with value of 75. There we also use the CurrentFrameImageProperty dependency property we created earlier to command that this property and its value will be changed and whatever event connected to it will be triggered. In this case, the PropertyMetaData that holds the signature of OnCurrentFrameImageChanged will be triggered.
If you notice that we set the repeat behavior to Forever as it will be looping the animation forever, as long as the user commands to start the animation. Only the user know when to stop it, that is the reason why we also need to create a corresponding StopAnimation method.
For StopAnimation method, please see the implementation below.
public void StopAnimation()
{
if (this.IsAnimating)
{
this.BeginAnimation(Image.SourceProperty,
null);
this.IsAnimating = false;
}
}
Actual Control Implementation
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
namespace CodesDirectory.Controls
{
public partial class AnimatableImage
: Image
{
#region Fields
private static readonly DependencyProperty
CurrentFrameImageProperty =
DependencyProperty.Register("CurrentFrameImage",
typeof(int),
typeof(AnimatableImage),
new PropertyMetadata(0,
AnimatableImage.OnCurrentFrameImageChanged));
#endregion
#region
Privates
private const int TIME_MILLISECONDS_PER_IMAGEFRAME = 75;
#endregion
public AnimatableImage()
{
InitializeComponent();
}
#region
Methods
private void
InitializeImageFrames()
{
if (object.ReferenceEquals(this.Source, null)) return;
BitmapFrame bf = this.Source
as BitmapFrame;
if (object.ReferenceEquals(bf,
null)) bf = BitmapFrame.Create((BitmapSource)this.Source);
if (!object.ReferenceEquals(bf.Decoder,
null))
{
this.FrameCount =
bf.Decoder.Frames.Count;
this.IsFramesInitiated = true;
}
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs
e)
{
if (object.Equals(e.Property,
Image.SourceProperty))
{
this.InitializeImageFrames();
base.OnPropertyChanged(e);
}
}
public void
StartAnimate()
{
if (!this.IsAnimating
&& this.IsFramesInitiated)
{
Int32Animation animation = new Int32Animation(0,
this.FrameCount - 1, new
Duration(TimeSpan.FromMilliseconds(AnimatableImage.TIME_MILLISECONDS_PER_IMAGEFRAME *
this.FrameCount)));
animation.RepeatBehavior = RepeatBehavior.Forever;
this.BeginAnimation(CurrentFrameImageProperty,
animation, HandoffBehavior.SnapshotAndReplace);
this.IsAnimating = true;
}
}
public void
StopAnimation()
{
if (this.IsAnimating)
{
this.BeginAnimation(Image.SourceProperty, null);
this.IsAnimating = false;
}
}
private static void OnCurrentFrameImageChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs
e)
{
AnimatableImage image = (AnimatableImage)dpo;
image.Source = ((BitmapFrame)image.Source).Decoder.Frames[(int)e.NewValue];
}
#endregion
#region
Properties
public bool IsAnimating { get;
private set; }
public int FrameCount
{ get; private set; }
private bool
IsFramesInitiated { get; set; }
#endregion
}
}
For further details and implementation please visit Animating an Image in WPF using Timer.
No comments:
Post a Comment
Place your comments and ideas