Elad’s WPF Blog

April 2, 2009

Animated GIF Support Behavior

Filed under: Programming, WPF — Tags: , , , — Elad Malki @ 4:50 pm

After talking about Attached Behavior in the last post, this is an example of a behavior which adds an animated GIF support to regular WPF Image control.

WPF does not support naturally displaying an animated GIF. There are couple of ways to overcome this problem, and using this behavior is one of them. Basically, the behavior creates an animation that loops over the animated gif frames, setting them as a source for the image.

you can download the source and sample here

The usage example is:

<Window x:Class="AnimatedGifBehaviorTest.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:AnimatedGifBehaviorTest"
        
    Title="Window2" Height="300" Width="300">
    <Grid>
            <Image local:SupportAnimatedGIFBehviour.SupportAnimatedGif="True"
                   Source="MyImage.gif" Width="32" Height="32"/>
    </Grid>
</Window>

And the behavior implementation is:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;

namespace AnimatedGifBehaviorTest
{
    public static class SupportAnimatedGIFBehviour
    {
        private static readonly int MILLISCONDS_PER_FRAME = 75;

        #region SupportAnimatedGif Attached Property

        [AttachedPropertyBrowsableForType(typeof(Image))]
        public static bool GetSupportAnimatedGif(Image image)
        {
            return (bool)image.GetValue(SupportAnimatedGifProperty);
        }

        public static void SetSupportAnimatedGif(Image image, bool value)
        {
            image.SetValue(SupportAnimatedGifProperty, value);
        }

        /// <summary>
        /// An Attached Property for Animated GIF support.
        /// </summary>
        public static readonly DependencyProperty SupportAnimatedGifProperty =
            DependencyProperty.RegisterAttached(
            "SupportAnimatedGif",
            typeof(bool),
            typeof(SupportAnimatedGIFBehviour),
            new UIPropertyMetadata(false, OnSupportAnimatedGifChanged));

        /// <summary>
        /// Register \ UnRegister to visibility changes and image source changes.
        /// </summary>
        /// <param name="depObj">The image object.</param>
        /// <param name="e">The SupportAnimatedGif Property values.</param>
        private static void OnSupportAnimatedGifChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            bool isSupportingAnimatedGIF = (bool)e.NewValue;
            Image image = (Image)depObj;

            if (isSupportingAnimatedGIF)
            {
                RegisterForRelevantImagePropertyChanges(image);
                
                if (image.Visibility == Visibility.Visible)
                {
                    image.StartFramesAnimation();
                }
            }
            else
            {
                UnRegisterForRelevantImagePropertyChanges(image);
                StopFramesAnimation(image);
            }
        }

        private static DependencyPropertyDescriptor VisibilityDPDescriptor
        {
            get
            {
                return DependencyPropertyDescriptor.FromProperty(UIElement.VisibilityProperty, typeof(UIElement));
            }
        }

        private static DependencyPropertyDescriptor ImageSourceDPDescriptor
        {
            get
            {
                return DependencyPropertyDescriptor.FromProperty(Image.SourceProperty, typeof(Image));
            }
        }

        private static void UnRegisterForRelevantImagePropertyChanges(DependencyObject depObj)
        {
            VisibilityDPDescriptor.RemoveValueChanged(depObj, OnVisibilityChanged);
            ImageSourceDPDescriptor.RemoveValueChanged(depObj, OnImageSourceChanged);            
        }

        private static void RegisterForRelevantImagePropertyChanges(DependencyObject depObj)
        {
            VisibilityDPDescriptor.AddValueChanged(depObj, OnVisibilityChanged);
            ImageSourceDPDescriptor.AddValueChanged(depObj, OnImageSourceChanged);
        }

        #endregion

        #region CurrentFrameIndex Dependency Property

        private static readonly DependencyProperty CurrentFrameIndexProperty = DependencyProperty.Register(
            "CurrentFrameIndex",
            typeof(int),
            typeof(Image),
            new PropertyMetadata(0, OnCurrentFrameIndexChanged));

        #endregion

        #region IsAnimationChangingFrame Dependency Property

        /// <summary>
        /// IsAnimationChangingFrame Dependency Property
        /// </summary>
        private static readonly DependencyProperty IsAnimationChangingFrameProperty = DependencyProperty.Register(
            "IsAnimationChangingFrame",
            typeof(bool),
            typeof(Image),
            new PropertyMetadata(false));

        /// <summary>
        /// DummyImage Dependency Property - for keeping the original source binding when animation is applied
        /// </summary>
        private static readonly DependencyProperty DummyImageProperty = DependencyProperty.Register(
            "DummyImage",
            typeof(ImageSource),
            typeof(Image),
            new PropertyMetadata(null, OnDummyImagePropertyChanged));

        /// <summary>
        /// Gets or sets the IsAnimationChangingFrame property. This dependency property 
        /// indicates that the animation is currently changing the image source.
        /// </summary>
        private static bool GetIsAnimationChangingFrame(this Image image)
        {
            return (bool)image.GetValue(IsAnimationChangingFrameProperty);
        }
        private static void SetIsAnimationChangingFrame(this Image image, bool isAnimationChangingFrame)
        {
            image.SetValue(IsAnimationChangingFrameProperty, isAnimationChangingFrame);
        }

        #endregion

        private static void OnImageSourceChanged(object sender, EventArgs e)
        {
            Image image = (Image)sender;

            if (!image.GetIsAnimationChangingFrame())//If the image source is changing by an outer source(not the animation).
            {
                //stop old animation
                image.SetIsAnimationChangingFrame(true);
                image.StopFramesAnimation();
                image.SetIsAnimationChangingFrame(false);

                //start new animation - only if frames count is bigger than one.
                image.StartFramesAnimation();
            }
        }

        /// <summary>
        /// Starts frame animation only if frames count is bigger than 1.
        /// </summary>
        /// <param name="image"></param>
        private static void StartFramesAnimation(this Image image)
        {
            BitmapFrame bitmapFrame = image.Source as BitmapFrame;
            if (bitmapFrame != null)
            {
                int framesCount = bitmapFrame.Decoder.Frames.Count;

                if (framesCount > 1)
                {
                    Int32Animation gifAnimation =
                        new Int32Animation(
                            0, // "From" value
                            framesCount - 1, // "To" value
                            new Duration(TimeSpan.FromMilliseconds(MILLISCONDS_PER_FRAME * framesCount))
                        );

                    gifAnimation.RepeatBehavior = RepeatBehavior.Forever;

                    image.BeginAnimation(CurrentFrameIndexProperty, gifAnimation, HandoffBehavior.SnapshotAndReplace);
                }
            }
        }

        private static void StopFramesAnimation(this Image image)
        {
            image.BeginAnimation(CurrentFrameIndexProperty, null);
        }

        private static void OnVisibilityChanged(object sender, EventArgs e)
        {
            Image image = (Image)sender;

            if (image.Visibility != Visibility.Visible)
            {
                image.StopFramesAnimation();
            }
            else
            {
                image.StartFramesAnimation();
            }
        }

        private static void OnDummyImagePropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
        {
            Image animatedImage = (Image)dpo;

            if (!animatedImage.GetIsAnimationChangingFrame())
            {
                BindingBase originalBinding = BindingOperations.GetBindingBase(dpo, DummyImageProperty);
                if (originalBinding != null)
                {
                    BindingOperations.SetBinding(dpo, Image.SourceProperty, originalBinding);
                    BindingOperations.ClearBinding(animatedImage, DummyImageProperty);
                }
                animatedImage.SetIsAnimationChangingFrame(false);
            }
            else
            {
                animatedImage.SetIsAnimationChangingFrame(false);
            }

            animatedImage.SetIsAnimationChangingFrame(false);
        }

        /// <summary>
        /// Update the current image source to the relevenat frame.
        /// </summary>
        /// <param name="dpo"></param>
        /// <param name="e"></param>
        private static void OnCurrentFrameIndexChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
        {
            Image animatedImage = (Image)dpo;

            if (!animatedImage.GetIsAnimationChangingFrame())
            {
                animatedImage.SetIsAnimationChangingFrame(true);

                bool hasBinding = BindingOperations.IsDataBound(animatedImage, Image.SourceProperty);
                if (hasBinding)
                {
                    BindingBase originalBinding = BindingOperations.GetBindingBase(animatedImage, Image.SourceProperty);
                    BindingOperations.SetBinding(animatedImage, DummyImageProperty, originalBinding);
                }
                animatedImage.Source = ((BitmapFrame)animatedImage.Source).Decoder.Frames[(int)e.NewValue];

                if (!hasBinding)
                {
                    animatedImage.SetIsAnimationChangingFrame(false);
                }
            }
        }
    }
}

Attached Behavior

Filed under: Programming, WPF — Tags: , , — Elad Malki @ 8:04 am

WPF has a powerful concept of Attached Properties. Since a Dependency Object is a kind of a sophisticated property bag, it can contain its own properties and other properties. The traditional use of attached properties is by the object that declare it, for example, A grid has a Row and a Column attached properties. It looks up these properties on every child of his, and use their values to arrange the children:

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition/>
    <RowDefinition/>
  </Grid.RowDefinitions>
  <Button Grid.Row="0">Button 1</Button>
  <Button Grid.Row="1">Button 2</Button>
</Grid>

A more advanced way of using attached properties is Attached Behaviors. An attached behavior is a way of extending or adding functionality of a dependency object (you can find more about it in Josh Smith’s article). For example, we might have a scenario in which we want to select the text of a TextBox when it gets the focus. A solution for this scenario could be hooking to the appropriate events in the code behind \ ViewModel of the TextBox and selecting the text when needed. There are two problems with this solution:

1. It requires writing code in an "inappropriate" WPF places such as code behind (which is always better to leave empty) or a ViewModel (which should contain info and logic of the view rather than technical stuff).

2. It requires writing a lot of code many times if we have more than one textbox, or several textboxes in different windows that needs this behavior.

A better solution would be writing an attached behavior. An attached behavior in WPF is normally a static class with one or more attached properties. The trick in this class is to listen to the change event of one of the properties, and to act upon it. This is an example of the Textbox "text selection on focus" behavior (Based on Eric Burke’s Post):

public static class SelectAllOnFocusBehaviour
{
        /// 1. This is the boolean attached property with its getter and setter:
        public static readonly DependencyProperty SelectAllOnFocusProperty =
            DependencyProperty.RegisterAttached
            (
                "SelectAllOnFocus",
                typeof(bool),
                typeof(SelectAllOnFocusBehaviour),
                new UIPropertyMetadata(false, OnSelectAllOnFocusPropertyChanged)
            );
        public static bool GetSelectAllOnFocus(DependencyObject obj)
        {
            return (bool)obj.GetValue(SelectAllOnFocusProperty);
        }
        public static void SetSelectAllOnFocus(DependencyObject obj, bool value)
        {
            obj.SetValue(SelectAllOnFocusProperty, value);
        }

        /// 2. This is the change event of our attached property value:
        ///     * We get in the first parameter the dependency object to which the attached behavior was attached
        ///     * We get in the second parameter the value of the attached behavior.
        ///     * The implementation of the behavior is to check if we are attached to a textBox, and if so and the value of the behavior
        ///       is true, hook to the PreviewGotKeyboardFocus of the textbox.
        private static void OnSelectAllOnFocusPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs args)
        {
            TextBoxBase textBox = dpo as TextBoxBase;
            if (textBox != null)
            {
                TextBoxBase tbb = dpo as TextBoxBase;
                if ((bool)args.NewValue)
                {
                    textBox.PreviewGotKeyboardFocus += OnTextBoxPreviewGotKeyboardFocus;
                }
                else
                {
                    textBox.PreviewGotKeyboardFocus -= OnTextBoxPreviewGotKeyboardFocus;
                }
            }
        }

        /// 3. The actual implementation: Whenever the textbox gets the keyboard focus, we select its text
        private static void OnTextBoxPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            TextBoxBase txtBox = (TextBoxBase)sender;
            Action action = () => { txtBox.SelectAll(); };
            txtBox.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
        }
    }

And this is an example of the behavior usage:

<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:behaviors="your behavior xmlns path">
  <Grid>
    <TextBox Text="Some Text" behaviors:SelectAllOnFocusBehaviour.SelectAllOnFocus="True"/>
  </Grid>
</Page>

The result is a much declarative, maintainable and shorter code. You can find many useful behaviors on the web.

In Silverlight 3.0 there is a built in behaviors infrastructure, by using the Behaviors (and\or Triggers) attached property which contains a collection of behaviors (or Triggers).

Elad.

Create a free website or blog at WordPress.com.