none
Fade the text in a Label RRS feed

  • Question

  • Is there a way to slowly fade the text in a label, change the text in the label, then fade it back in to make it more appealing when I change the text? Please answer with lots of detail. Thanks.
    Kettering University Co-Op davi9267@kettering.edu
    • Moved by OmegaMan Monday, April 27, 2009 3:30 PM (From:Visual C# General)
    Monday, April 27, 2009 2:48 PM

Answers

  • Ok, I've tinkered some more. The code below contains the following changes (if anyone's still paying attention):

    I removed ColorPropertyAnimation and instead created a way to make value animators which could be used to animate almost anything through just the PropertyAnimation. I included ColorAnimator, DoubleAnimator, and IntegerAnimator as examples.

    Added DelayAnimation as an example of the simplest kind of animation possible (it does nothing but wait).

    The PropertyAnimation can deal with basic type conversions (for things like animating the Value field of a NumericUpDown control whose type is decimal).

    I added lots of, hopefully useful, comments.

    The code is now all in a single file, along with a simple test form to play with. This makes it easy to copy from here and see in action, just follow these three easy steps:

    Create a new Windows (forms) Application.
    Delete the stock Form1 from the SolutionExplorer (or not, it'll be ignored).
    Replace the entire contents of Program.cs with the following code.

    using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Drawing;
    using System.Reflection;
    using System.Windows.Forms;
    
    #region Test Form
    
    public class TestForm : Form
    {
        #region Main
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new TestForm());
        }
        #endregion
    
        #region Form Initialization
        private System.ComponentModel.IContainer components = null;
        private AnimationManager animationManager1;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private Button button3;
        private Label label1;
        private System.Windows.Forms.NumericUpDown numericUpDown1;
    
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }
    
        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.animationManager1 = new AnimationManager(this.components);
            this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
            this.button3 = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(12, 74);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(260, 20);
            this.textBox1.TabIndex = 1;
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(24, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(105, 12);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 2;
            this.button2.Text = "button2";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // numericUpDown1
            // 
            this.numericUpDown1.Location = new System.Drawing.Point(105, 111);
            this.numericUpDown1.Name = "numericUpDown1";
            this.numericUpDown1.Size = new System.Drawing.Size(75, 20);
            this.numericUpDown1.TabIndex = 3;
            // 
            // button3
            // 
            this.button3.Location = new System.Drawing.Point(186, 12);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(75, 23);
            this.button3.TabIndex = 2;
            this.button3.Text = "button3";
            this.button3.UseVisualStyleBackColor = true;
            this.button3.Click += new System.EventHandler(this.button3_Click);
            // 
            // label1
            // 
            this.label1.Location = new System.Drawing.Point(12, 48);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(222, 23);
            this.label1.TabIndex = 4;
            // 
            // TestForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 264);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.numericUpDown1);
            this.Controls.Add(this.button3);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.textBox1);
            this.Name = "TestForm";
            this.Text = "Animating in WinForms";
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();
    
        }
        #endregion
    
        public TestForm()
        {
            InitializeComponent();
        }
        #endregion
    
        private void button1_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Pick textBox1 or label1 to see the difference
            Control control = label1;
    
            // Reset control to initial values
            Color startColor = SystemColors.ControlText;
            control.Text = "Goodbye, cruel World...";
            control.ForeColor = startColor;
    
            // Add an animation sequence consisting of a delay, 'fade out', text change, and 'fade in'
            animationManager1.Add(
                new AnimationSequence(
                    new DelayAnimation(TimeSpan.FromSeconds(1))
                    , new PropertyAnimation("ForeColor", control, control.BackColor, TimeSpan.FromSeconds(2), new ColorAnimator())
                    , new PropertyAnimation("Text", control, "Hello, World!")
                    , new PropertyAnimation("ForeColor", control, startColor, TimeSpan.FromSeconds(1), new ColorAnimator())
                ));
        }
    
        private void button2_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Reset numericUpDown1 to initial values
            numericUpDown1.Value = 1;
    
            // Add an animation to change the Value of the control
            animationManager1.Add(new PropertyAnimation("Value", numericUpDown1, 10, TimeSpan.FromSeconds(10), new IntegerAnimator()));
        }
    
        private void button3_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Reset textBox1 to initial values
            textBox1.Text = "99";
    
            // The type conversion code in the PropertyAnimation allows even something odd like using an IntegerAnimator on a
            //  string property (though admittedly, this is more a side effect than a feature).
            animationManager1.Add(new PropertyAnimation("Text", textBox1, "90", TimeSpan.FromSeconds(5), new IntegerAnimator()));
        }
    }
    #endregion
    
    
    #region Animation Management
    
    // The AnimationManager componant allows you to add animations to your container
    
    public class AnimationManager : Component
    {
        protected System.ComponentModel.IContainer components = null;
        protected List<Animation> activeAnimations = new List<Animation>();
        protected System.Windows.Forms.Timer timer1;
    
        public AnimationManager()
        {
            InitializeComponent();
        }
    
        public AnimationManager(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
        }
    
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }
    
        protected virtual void InitializeComponent()
        {
            // This componant uses a Timer componant for generating animation pulses
    
            this.components = new System.ComponentModel.Container();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.timer1.Enabled = true;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            this.timer1.Interval = 50; // 20 times per second is pretty smooth for color changes
        }
    
        // This is the time interval (in ms) of the animation pulses. It has no effect on
        //  the timing of animations, but can be increased or decreased to adjust how
        //  course or fine the animations run.
        [Description("The time interval of the animation pulses in milliseconds.")]
        [DefaultValue(50)]
        public int PulseInterval
        {
            get { return timer1.Interval; }
            set { timer1.Interval = value; }
        }
    
        // Returns true if there are currently any animations playing.
        [Browsable(false)]
        public bool Animating { get { return activeAnimations.Count > 0; } }
    
        // Adding an animation will begin it immediately.
        public void Add(Animation animation)
        {
            if (animation == null)
                throw new ArgumentNullException();
            animation.StartTime = DateTime.Now;
            animation.Begin();
            if (animation.IsComplete)
                animation.End();
            else
                activeAnimations.Add(animation);
        }
    
        // The only way to abort an individual animation is to call the Remove method here.
        public void Remove(Animation animation)
        {
            if (animation == null)
                throw new ArgumentNullException();
            if (activeAnimations.Contains(animation))
            {
                activeAnimations.Remove(animation);
                animation.End();
            }
        }
    
        // Stops all animations.
        public void Clear()
        {
            foreach (Animation animation in activeAnimations)
                animation.End();
            activeAnimations.Clear();
        }
    
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (activeAnimations.Count > 0)
            {
                List<Animation> deletions = null;
    
                // Enumerate all active animations and invoke their Pulse method
                foreach (Animation animation in activeAnimations)
                {
                    TimeSpan elapsed = DateTime.Now - animation.StartTime;
                    animation.Pulse(elapsed);
                    // If the animation has run its course, end it and add it to the deletion list
                    if (animation.IsComplete)
                    {
                        animation.End();
                        // Creating the deletion list here so that pulses occurring when there
                        //  are no deletions incur minimal cost
                        if (deletions == null)
                            deletions = new List<Animation>();
                        deletions.Add(animation);
                    }
                }
    
                // We use a separate list for deletions to simplify enumerating and deleting which is not
                //  allowed on the same list object.
                if (deletions != null)
                    foreach (Animation animation in deletions)
                        activeAnimations.Remove(animation);
            }
        }
    }
    
    
    // An animation is anything that has a beginning, "runs" for a period of time (receiving periodic
    //  pulses), and then ends.
    
    public abstract class Animation
    {
        // Time the animation began. This is set by the AnimationManager.
        public DateTime StartTime { get; internal set; }
    
        // Has the animation run its course? The animation itself is responsible for setting this to true.
        public bool IsComplete { get; protected set; }
        
        // Called by the AnimationManager when the animation is started. If IsComplete is set to true here
        //  the End method will be called and the Pulse method will never be called. Handy if your "animation"
        //  has a time duration of zero.
        public abstract void Begin();
        
        // Called by the AnimationManager periodically during the animation.
        public abstract void Pulse(TimeSpan elapsed);
        
        // Called by the AnimationManager to signal the animation is done. It is important that
        //  the animation make a final state change to the object it is animating here. That is to say, set
        //  the object's state to be what it should be at the end of the animation.
        public abstract void End();
    }
    
    
    // An AnimationSequence is an ordered list of animations. Each executes completely, one after the other.
    
    public sealed class AnimationSequence : Animation
    {
        private Queue<Animation> queue;
        private Animation currentAnimation = null;
    
        public AnimationSequence(params Animation[] animations)
        {
            if (animations == null)
                throw new ArgumentNullException();
            queue = new Queue<Animation>(animations);
        }
    
        public override void Begin()
        {
            // This "animation" is complete, only after all of the animations in the list complete.
            // The odd logic here is in the case of an animation calling itself complete after
            //  we call Begin (a zero-length animation), so we have to return to the queue for
            //  the next one.
    
        TryNext:
            if (queue.Count == 0)
                IsComplete = true;
            else
            {
                currentAnimation = queue.Dequeue();
                currentAnimation.StartTime = DateTime.Now;
                currentAnimation.Begin();
                if (currentAnimation.IsComplete)
                {
                    currentAnimation.End();
                    goto TryNext;
                }
            }
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            // This should not fire because we should not get a Pulse call if we have no running animation.
            Debug.Assert(currentAnimation != null, "currentAnimation != null");
    
            // Fire off the pulse for the current animation. Note that we pass an elapsed
            //  time of Now minus the animation's start time, not the elapsed value passed
            // to us which is the amount of time elapsed for the entire sequence (so far).
            elapsed = DateTime.Now - currentAnimation.StartTime;
            currentAnimation.Pulse(elapsed);
    
            // If the current animation has completed
            if (currentAnimation.IsComplete)
            {
                // Fire off its End
                currentAnimation.End();
                currentAnimation = null;
                // Queue up next animation
                Begin();
            }
        }
    
        public override void End()
        {
            // If End is called (on us) before all of our animations have ended,
            //  we need to call End on each of the remaining ones so that they have the
            //  opportunity to set their animation objects to their final state.
            while (currentAnimation != null)
            {
                currentAnimation.End();
                currentAnimation = queue.Count > 0 ? queue.Dequeue() : null;
            }
        }
    }
    #endregion
    
    #region Animations
    
    
    // DelayAnimation is an animation that does nothing but delay for the specified amount of time.
    
    public class DelayAnimation : Animation
    {
        TimeSpan delay;
        public DelayAnimation(TimeSpan delay)
        {
            this.delay = delay;
        }
    
        public override void Begin()
        {
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            if (elapsed >= delay)
                IsComplete = true;
        }
    
        public override void End()
        {
        }
    }
    
    
    // A PropertyAnimation allows any (non-indexed.. for now) object property to be animated
    // using an IAnimator object
    
    public class PropertyAnimation : Animation
    {
        protected object instance;
        protected object startValue, endValue;
        protected string propertyName;
        protected PropertyInfo propertyInfo;
        protected IAnimator animator;
        protected TimeSpan duration;
    
        // This ctor creates a zero-length animation of the property change (i.e. it'll change
        //  immediately). This is useful if you want to change a property during an AnimationSequence.
        public PropertyAnimation(string propertyName, object instance, object endValue)
            : this(propertyName, instance, endValue, TimeSpan.Zero, null)
        {
        }
    
        // Create an animation on an object's property. The animator determains how the property
        //  morphs over time.
        public PropertyAnimation(string propertyName, object instance, object endValue, TimeSpan duration, IAnimator animator)
        {
            if (String.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");
            if (instance == null)
                throw new ArgumentNullException("instance");
            this.propertyName = propertyName;
            this.instance = instance;
            this.propertyInfo = instance.GetType().GetProperty(propertyName);
            // Assure that the endValue passed is of the correct type to be set to the target property
            this.endValue = Convert.ChangeType(endValue, propertyInfo.PropertyType);
            this.animator = animator;
            this.duration = duration;
        }
    
        public override void Begin()
        {
            // If no animator is specified or the duration is zero, flag ourselves complete so
            //  that Pulse is not called.
            if (animator == null || duration == TimeSpan.Zero)
                IsComplete = true;
            // Otherwise we cache the initial value of the property for use during Pulse
            else
                this.startValue = propertyInfo.GetValue(instance, null);
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            // If time has expired, we are done
            if (elapsed >= duration)
                IsComplete = true;
            // Otherwise set the property to an intermediate value, determined by the Animator
            else
            {
                // Calculate the "percentage done" for this pulse
                float fraction = (float)elapsed.Ticks / (float)duration.Ticks;
    
                // Let the Animator do the work of generating an intermediate value
                object temp = animator.ComputeValue(startValue, endValue, fraction);
    
                // Assure that the Animator's return value is of a compatible type
                temp = Convert.ChangeType(temp, propertyInfo.PropertyType);
    
                // Change the property
                propertyInfo.SetValue(instance, temp, null);
            }
        }
    
        public override void End()
        {
            // Change the property to its final value
            propertyInfo.SetValue(instance, endValue, null);
        }
    }
    #endregion
    
    
    #region Animators
    
    
    // IAnimator is the workhorse for animating values
    
    public interface IAnimator
    {
        // ComputeValue will receive a starting value, ending value, and a fraction
        //  representing a point between the two.
        // If given an amount of 0.0, this function is expected to return the startValue.
        // If given an amount of 1.0, this function is expected to return the endValue.
        // If given an amount between 0.0 and 1.0, this function should return a value that
        //  represents some appropriate "distance" between the two.
        object ComputeValue(object startValue, object endValue, float amount);
    }
    
    
    // The ColorAnimator returns a linear interpolation between two color values representing
    //  the distance between the two.
    
    public class ColorAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            Color color1 = (Color)startValue;
            Color color2 = (Color)endValue;
            int r1 = color1.R;
            int g1 = color1.G;
            int b1 = color1.B;
            int r2 = color2.R;
            int g2 = color2.G;
            int b2 = color2.B;
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            int r3 = r1 + (((r2 - r1) * n) >> 16);
            int g3 = g1 + (((g2 - g1) * n) >> 16);
            int b3 = b1 + (((b2 - b1) * n) >> 16);
    
            return Color.FromArgb(r3, g3, b3);
        }
    }
    
    // The DoubleAnimator returns a linear interpolation between two double values.
    public class DoubleAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            double s = Convert.ToDouble(startValue);
            double e = Convert.ToDouble(endValue);
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            double r = s + (((e - s) * n) / 65536);
            return r;
        }
    }
    
    // The IntegerAnimator returns a linear interpolation between two integer values.
    public class IntegerAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            int s = Convert.ToInt32(startValue);
            int e = Convert.ToInt32(endValue);
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            int r = s + (((e - s) * n) >> 16);
            return r;
        }
    }
    #endregion
    
    #region Math Helper
    internal static class MathHelper
    {
        // Clamps the value to min and max while also rounding the value.
        internal static double ClampAndRound(float value, float min, float max)
        {
            if (float.IsNaN(value))
                return 0.0;
            if (float.IsInfinity(value))
                return (float.IsNegativeInfinity(value) ? ((double)min) : ((double)max));
            if (value < min)
                return (double)min;
            if (value > max)
                return (double)max;
            return Math.Round((double)value);
        }
    }
    #endregion
    
    • Edited by Tergiver Tuesday, April 28, 2009 3:42 AM Minor changes, mainly cosmetic
    • Marked as answer by Xystus777 Friday, May 1, 2009 10:27 AM
    Tuesday, April 28, 2009 2:09 AM
  • Here's something smaller.  Add a new class to your project and paste the code shown below.  Compile.  Drop the new control from the top of the toolbox onto your form.  It is active in the designer, you should see the effect right away.

    using System;
    using System.Drawing;
    using System.Windows.Forms;

    public class FadeLabel : Label {
      private ColorAnimator mAnim = new ColorAnimator();
      private string mText = "";

      public FadeLabel() {
        mAnim.Change += new EventHandler(mAnim_Change);
      }
      public override Color ForeColor {
        get { return base.ForeColor; }
        set { base.ForeColor = value; mAnim.Color = value; }
      }
      public override string Text {
        get { return base.Text; }
        set { if (base.Text != "") mAnim.Begin(); mText = base.Text; base.Text = value; }
      }
      void mAnim_Change(object sender, EventArgs e) {
        Invalidate();
      }
      protected override void OnPaint(PaintEventArgs e) {
        string txt = mAnim.Fading ? mText : base.Text;
        using (Brush br = new SolidBrush(mAnim.Color))
          e.Graphics.DrawString(txt, this.Font, br, this.ClientRectangle);
      }

      internal class ColorAnimator : Timer {
        public event EventHandler Change;
        private const int cRate = 5;    // Tweak this
        private Color mColor;
        private int mValue = 255;
        private int mStep = -1;

        public Color Color {
          get { return Color.FromArgb(mValue, mColor); }
          set { mColor = value; }
        }
        public void Begin() {
          mValue = 255;
          mStep = -cRate;
          Interval = 16;
          Enabled = true;
        }
        public bool Fading {
          get { return Enabled && mStep < 0; }
        }
        protected override void OnTick(EventArgs e) {
          mValue += mStep;
          if (mValue <= 0) { mValue = 0; mStep = -mStep; }
          if (mValue >= 255) { mValue = 255; Enabled = false; }
          if (Change != null) Change(this, EventArgs.Empty);
        }
      }
    }
    Hans Passant.
    • Marked as answer by Xystus777 Friday, May 1, 2009 10:27 AM
    Thursday, April 30, 2009 9:34 PM
    Moderator

All replies

  • Basically what you want to do is "morph" the forground color into the background color over time (keep in mind that by default, the window colors come from the user's system preferences), and then back again.

    You can use a timer componant to get the timing pulses.

    You want to first determine the number of steps (where each step occurs in N milliseconds). Both the number of steps and the time between pulses together will determine the total "fade time".

    Save the original ForeColor value for subsequent operations.

    At each pulse increment a "step value" and use a linear interpolation of the two colors (original ForeColor and BackColor) with the current step value.

    To fade in, just do the reverse.

    There may be a few gotchas if you allow the user to interact with the form during animation, so test it abusively.

    As for the linear interpolation, XNA has a nice Color.Lerp method, and WPF has one as well, but there isn't one built in for a WinForms app. It's not that difficult an algorithm, just remember to do it for each Red, Green, and Blue value in the color. If you really struggle with this let me know and I'll whip something up.
    • Proposed as answer by Lenny S Monday, April 27, 2009 9:16 PM
    • Unproposed as answer by Xystus777 Friday, May 1, 2009 11:21 AM
    Monday, April 27, 2009 3:41 PM
  • I'll give that a shot, thanks!
    Kettering University Co-Op davi9267@kettering.edu
    Monday, April 27, 2009 3:43 PM
  • Here's something interesting I found by googling "animating properties in winforms":

    http://www.codeproject.com/kb/miscctrl/animators.aspx

    I have not checked it out, but it sounds like it can do what you need.

    Monday, April 27, 2009 3:53 PM
  • So I thought this would be a fun thing to play with and whipped something up. It's not the best way to handle animations (I'd prefer to make a generic property animator that you can pass a value animator to), but this is simple and functional.

    * edited to remove lengthy and obsolete code, see post below
    • Edited by Tergiver Tuesday, April 28, 2009 2:11 AM
    Monday, April 27, 2009 9:04 PM
  • The AnimationSequence above has a bug. We would like any animations being prematurely terminated (like calling AnimationManager.Clear) to complete their End methods so that no animating objects are left in an intermediate state. The fixed class should be:

    * edited to remove lengthy and obsolete code, see post below
    • Edited by Tergiver Tuesday, April 28, 2009 2:11 AM
    Monday, April 27, 2009 10:24 PM
  • Ok, I've tinkered some more. The code below contains the following changes (if anyone's still paying attention):

    I removed ColorPropertyAnimation and instead created a way to make value animators which could be used to animate almost anything through just the PropertyAnimation. I included ColorAnimator, DoubleAnimator, and IntegerAnimator as examples.

    Added DelayAnimation as an example of the simplest kind of animation possible (it does nothing but wait).

    The PropertyAnimation can deal with basic type conversions (for things like animating the Value field of a NumericUpDown control whose type is decimal).

    I added lots of, hopefully useful, comments.

    The code is now all in a single file, along with a simple test form to play with. This makes it easy to copy from here and see in action, just follow these three easy steps:

    Create a new Windows (forms) Application.
    Delete the stock Form1 from the SolutionExplorer (or not, it'll be ignored).
    Replace the entire contents of Program.cs with the following code.

    using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Drawing;
    using System.Reflection;
    using System.Windows.Forms;
    
    #region Test Form
    
    public class TestForm : Form
    {
        #region Main
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new TestForm());
        }
        #endregion
    
        #region Form Initialization
        private System.ComponentModel.IContainer components = null;
        private AnimationManager animationManager1;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private Button button3;
        private Label label1;
        private System.Windows.Forms.NumericUpDown numericUpDown1;
    
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }
    
        #region Windows Form Designer generated code
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.animationManager1 = new AnimationManager(this.components);
            this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
            this.button3 = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
            this.SuspendLayout();
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(12, 74);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(260, 20);
            this.textBox1.TabIndex = 1;
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(24, 12);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(105, 12);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 2;
            this.button2.Text = "button2";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // numericUpDown1
            // 
            this.numericUpDown1.Location = new System.Drawing.Point(105, 111);
            this.numericUpDown1.Name = "numericUpDown1";
            this.numericUpDown1.Size = new System.Drawing.Size(75, 20);
            this.numericUpDown1.TabIndex = 3;
            // 
            // button3
            // 
            this.button3.Location = new System.Drawing.Point(186, 12);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(75, 23);
            this.button3.TabIndex = 2;
            this.button3.Text = "button3";
            this.button3.UseVisualStyleBackColor = true;
            this.button3.Click += new System.EventHandler(this.button3_Click);
            // 
            // label1
            // 
            this.label1.Location = new System.Drawing.Point(12, 48);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(222, 23);
            this.label1.TabIndex = 4;
            // 
            // TestForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 264);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.numericUpDown1);
            this.Controls.Add(this.button3);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Controls.Add(this.textBox1);
            this.Name = "TestForm";
            this.Text = "Animating in WinForms";
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();
    
        }
        #endregion
    
        public TestForm()
        {
            InitializeComponent();
        }
        #endregion
    
        private void button1_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Pick textBox1 or label1 to see the difference
            Control control = label1;
    
            // Reset control to initial values
            Color startColor = SystemColors.ControlText;
            control.Text = "Goodbye, cruel World...";
            control.ForeColor = startColor;
    
            // Add an animation sequence consisting of a delay, 'fade out', text change, and 'fade in'
            animationManager1.Add(
                new AnimationSequence(
                    new DelayAnimation(TimeSpan.FromSeconds(1))
                    , new PropertyAnimation("ForeColor", control, control.BackColor, TimeSpan.FromSeconds(2), new ColorAnimator())
                    , new PropertyAnimation("Text", control, "Hello, World!")
                    , new PropertyAnimation("ForeColor", control, startColor, TimeSpan.FromSeconds(1), new ColorAnimator())
                ));
        }
    
        private void button2_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Reset numericUpDown1 to initial values
            numericUpDown1.Value = 1;
    
            // Add an animation to change the Value of the control
            animationManager1.Add(new PropertyAnimation("Value", numericUpDown1, 10, TimeSpan.FromSeconds(10), new IntegerAnimator()));
        }
    
        private void button3_Click(object sender, EventArgs e)
        {
            // Stop any active animations
            animationManager1.Clear();
    
            // Reset textBox1 to initial values
            textBox1.Text = "99";
    
            // The type conversion code in the PropertyAnimation allows even something odd like using an IntegerAnimator on a
            //  string property (though admittedly, this is more a side effect than a feature).
            animationManager1.Add(new PropertyAnimation("Text", textBox1, "90", TimeSpan.FromSeconds(5), new IntegerAnimator()));
        }
    }
    #endregion
    
    
    #region Animation Management
    
    // The AnimationManager componant allows you to add animations to your container
    
    public class AnimationManager : Component
    {
        protected System.ComponentModel.IContainer components = null;
        protected List<Animation> activeAnimations = new List<Animation>();
        protected System.Windows.Forms.Timer timer1;
    
        public AnimationManager()
        {
            InitializeComponent();
        }
    
        public AnimationManager(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
        }
    
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();
            base.Dispose(disposing);
        }
    
        protected virtual void InitializeComponent()
        {
            // This componant uses a Timer componant for generating animation pulses
    
            this.components = new System.ComponentModel.Container();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            this.timer1.Enabled = true;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            this.timer1.Interval = 50; // 20 times per second is pretty smooth for color changes
        }
    
        // This is the time interval (in ms) of the animation pulses. It has no effect on
        //  the timing of animations, but can be increased or decreased to adjust how
        //  course or fine the animations run.
        [Description("The time interval of the animation pulses in milliseconds.")]
        [DefaultValue(50)]
        public int PulseInterval
        {
            get { return timer1.Interval; }
            set { timer1.Interval = value; }
        }
    
        // Returns true if there are currently any animations playing.
        [Browsable(false)]
        public bool Animating { get { return activeAnimations.Count > 0; } }
    
        // Adding an animation will begin it immediately.
        public void Add(Animation animation)
        {
            if (animation == null)
                throw new ArgumentNullException();
            animation.StartTime = DateTime.Now;
            animation.Begin();
            if (animation.IsComplete)
                animation.End();
            else
                activeAnimations.Add(animation);
        }
    
        // The only way to abort an individual animation is to call the Remove method here.
        public void Remove(Animation animation)
        {
            if (animation == null)
                throw new ArgumentNullException();
            if (activeAnimations.Contains(animation))
            {
                activeAnimations.Remove(animation);
                animation.End();
            }
        }
    
        // Stops all animations.
        public void Clear()
        {
            foreach (Animation animation in activeAnimations)
                animation.End();
            activeAnimations.Clear();
        }
    
        private void timer1_Tick(object sender, EventArgs e)
        {
            if (activeAnimations.Count > 0)
            {
                List<Animation> deletions = null;
    
                // Enumerate all active animations and invoke their Pulse method
                foreach (Animation animation in activeAnimations)
                {
                    TimeSpan elapsed = DateTime.Now - animation.StartTime;
                    animation.Pulse(elapsed);
                    // If the animation has run its course, end it and add it to the deletion list
                    if (animation.IsComplete)
                    {
                        animation.End();
                        // Creating the deletion list here so that pulses occurring when there
                        //  are no deletions incur minimal cost
                        if (deletions == null)
                            deletions = new List<Animation>();
                        deletions.Add(animation);
                    }
                }
    
                // We use a separate list for deletions to simplify enumerating and deleting which is not
                //  allowed on the same list object.
                if (deletions != null)
                    foreach (Animation animation in deletions)
                        activeAnimations.Remove(animation);
            }
        }
    }
    
    
    // An animation is anything that has a beginning, "runs" for a period of time (receiving periodic
    //  pulses), and then ends.
    
    public abstract class Animation
    {
        // Time the animation began. This is set by the AnimationManager.
        public DateTime StartTime { get; internal set; }
    
        // Has the animation run its course? The animation itself is responsible for setting this to true.
        public bool IsComplete { get; protected set; }
        
        // Called by the AnimationManager when the animation is started. If IsComplete is set to true here
        //  the End method will be called and the Pulse method will never be called. Handy if your "animation"
        //  has a time duration of zero.
        public abstract void Begin();
        
        // Called by the AnimationManager periodically during the animation.
        public abstract void Pulse(TimeSpan elapsed);
        
        // Called by the AnimationManager to signal the animation is done. It is important that
        //  the animation make a final state change to the object it is animating here. That is to say, set
        //  the object's state to be what it should be at the end of the animation.
        public abstract void End();
    }
    
    
    // An AnimationSequence is an ordered list of animations. Each executes completely, one after the other.
    
    public sealed class AnimationSequence : Animation
    {
        private Queue<Animation> queue;
        private Animation currentAnimation = null;
    
        public AnimationSequence(params Animation[] animations)
        {
            if (animations == null)
                throw new ArgumentNullException();
            queue = new Queue<Animation>(animations);
        }
    
        public override void Begin()
        {
            // This "animation" is complete, only after all of the animations in the list complete.
            // The odd logic here is in the case of an animation calling itself complete after
            //  we call Begin (a zero-length animation), so we have to return to the queue for
            //  the next one.
    
        TryNext:
            if (queue.Count == 0)
                IsComplete = true;
            else
            {
                currentAnimation = queue.Dequeue();
                currentAnimation.StartTime = DateTime.Now;
                currentAnimation.Begin();
                if (currentAnimation.IsComplete)
                {
                    currentAnimation.End();
                    goto TryNext;
                }
            }
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            // This should not fire because we should not get a Pulse call if we have no running animation.
            Debug.Assert(currentAnimation != null, "currentAnimation != null");
    
            // Fire off the pulse for the current animation. Note that we pass an elapsed
            //  time of Now minus the animation's start time, not the elapsed value passed
            // to us which is the amount of time elapsed for the entire sequence (so far).
            elapsed = DateTime.Now - currentAnimation.StartTime;
            currentAnimation.Pulse(elapsed);
    
            // If the current animation has completed
            if (currentAnimation.IsComplete)
            {
                // Fire off its End
                currentAnimation.End();
                currentAnimation = null;
                // Queue up next animation
                Begin();
            }
        }
    
        public override void End()
        {
            // If End is called (on us) before all of our animations have ended,
            //  we need to call End on each of the remaining ones so that they have the
            //  opportunity to set their animation objects to their final state.
            while (currentAnimation != null)
            {
                currentAnimation.End();
                currentAnimation = queue.Count > 0 ? queue.Dequeue() : null;
            }
        }
    }
    #endregion
    
    #region Animations
    
    
    // DelayAnimation is an animation that does nothing but delay for the specified amount of time.
    
    public class DelayAnimation : Animation
    {
        TimeSpan delay;
        public DelayAnimation(TimeSpan delay)
        {
            this.delay = delay;
        }
    
        public override void Begin()
        {
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            if (elapsed >= delay)
                IsComplete = true;
        }
    
        public override void End()
        {
        }
    }
    
    
    // A PropertyAnimation allows any (non-indexed.. for now) object property to be animated
    // using an IAnimator object
    
    public class PropertyAnimation : Animation
    {
        protected object instance;
        protected object startValue, endValue;
        protected string propertyName;
        protected PropertyInfo propertyInfo;
        protected IAnimator animator;
        protected TimeSpan duration;
    
        // This ctor creates a zero-length animation of the property change (i.e. it'll change
        //  immediately). This is useful if you want to change a property during an AnimationSequence.
        public PropertyAnimation(string propertyName, object instance, object endValue)
            : this(propertyName, instance, endValue, TimeSpan.Zero, null)
        {
        }
    
        // Create an animation on an object's property. The animator determains how the property
        //  morphs over time.
        public PropertyAnimation(string propertyName, object instance, object endValue, TimeSpan duration, IAnimator animator)
        {
            if (String.IsNullOrEmpty(propertyName))
                throw new ArgumentNullException("propertyName");
            if (instance == null)
                throw new ArgumentNullException("instance");
            this.propertyName = propertyName;
            this.instance = instance;
            this.propertyInfo = instance.GetType().GetProperty(propertyName);
            // Assure that the endValue passed is of the correct type to be set to the target property
            this.endValue = Convert.ChangeType(endValue, propertyInfo.PropertyType);
            this.animator = animator;
            this.duration = duration;
        }
    
        public override void Begin()
        {
            // If no animator is specified or the duration is zero, flag ourselves complete so
            //  that Pulse is not called.
            if (animator == null || duration == TimeSpan.Zero)
                IsComplete = true;
            // Otherwise we cache the initial value of the property for use during Pulse
            else
                this.startValue = propertyInfo.GetValue(instance, null);
        }
    
        public override void Pulse(TimeSpan elapsed)
        {
            // If time has expired, we are done
            if (elapsed >= duration)
                IsComplete = true;
            // Otherwise set the property to an intermediate value, determined by the Animator
            else
            {
                // Calculate the "percentage done" for this pulse
                float fraction = (float)elapsed.Ticks / (float)duration.Ticks;
    
                // Let the Animator do the work of generating an intermediate value
                object temp = animator.ComputeValue(startValue, endValue, fraction);
    
                // Assure that the Animator's return value is of a compatible type
                temp = Convert.ChangeType(temp, propertyInfo.PropertyType);
    
                // Change the property
                propertyInfo.SetValue(instance, temp, null);
            }
        }
    
        public override void End()
        {
            // Change the property to its final value
            propertyInfo.SetValue(instance, endValue, null);
        }
    }
    #endregion
    
    
    #region Animators
    
    
    // IAnimator is the workhorse for animating values
    
    public interface IAnimator
    {
        // ComputeValue will receive a starting value, ending value, and a fraction
        //  representing a point between the two.
        // If given an amount of 0.0, this function is expected to return the startValue.
        // If given an amount of 1.0, this function is expected to return the endValue.
        // If given an amount between 0.0 and 1.0, this function should return a value that
        //  represents some appropriate "distance" between the two.
        object ComputeValue(object startValue, object endValue, float amount);
    }
    
    
    // The ColorAnimator returns a linear interpolation between two color values representing
    //  the distance between the two.
    
    public class ColorAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            Color color1 = (Color)startValue;
            Color color2 = (Color)endValue;
            int r1 = color1.R;
            int g1 = color1.G;
            int b1 = color1.B;
            int r2 = color2.R;
            int g2 = color2.G;
            int b2 = color2.B;
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            int r3 = r1 + (((r2 - r1) * n) >> 16);
            int g3 = g1 + (((g2 - g1) * n) >> 16);
            int b3 = b1 + (((b2 - b1) * n) >> 16);
    
            return Color.FromArgb(r3, g3, b3);
        }
    }
    
    // The DoubleAnimator returns a linear interpolation between two double values.
    public class DoubleAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            double s = Convert.ToDouble(startValue);
            double e = Convert.ToDouble(endValue);
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            double r = s + (((e - s) * n) / 65536);
            return r;
        }
    }
    
    // The IntegerAnimator returns a linear interpolation between two integer values.
    public class IntegerAnimator : IAnimator
    {
        public object ComputeValue(object startValue, object endValue, float amount)
        {
            int s = Convert.ToInt32(startValue);
            int e = Convert.ToInt32(endValue);
            int n = (int)MathHelper.ClampAndRound(65536f * amount, 0f, 65536f);
            int r = s + (((e - s) * n) >> 16);
            return r;
        }
    }
    #endregion
    
    #region Math Helper
    internal static class MathHelper
    {
        // Clamps the value to min and max while also rounding the value.
        internal static double ClampAndRound(float value, float min, float max)
        {
            if (float.IsNaN(value))
                return 0.0;
            if (float.IsInfinity(value))
                return (float.IsNegativeInfinity(value) ? ((double)min) : ((double)max));
            if (value < min)
                return (double)min;
            if (value > max)
                return (double)max;
            return Math.Round((double)value);
        }
    }
    #endregion
    
    • Edited by Tergiver Tuesday, April 28, 2009 3:42 AM Minor changes, mainly cosmetic
    • Marked as answer by Xystus777 Friday, May 1, 2009 10:27 AM
    Tuesday, April 28, 2009 2:09 AM
  • Hey,

    That's quite a bit of code. I haven't had a chance to even look at this forum in a few days...ugghh. I'm hoping to work on it more on next Tuesday. Thanks for helping though!!! :) :) :)
    Kettering University Co-Op davi9267@kettering.edu
    Thursday, April 30, 2009 7:40 PM
  • That code is useable, if you drop it (sans the TestForm) into your project you could be animating propreties in a matter of seconds.

    I've actually done quite a bit of work on this including some important things like turning off the timer when there are no animations to play. It's no longer postable, it has grown quite a lot, but if you're really interested I'd be willing to post it elsewhere.

    Although I wrote an animation system for another C# project a couple of years ago, I like this one better. It's simpler.
    Thursday, April 30, 2009 7:54 PM
  • Here's something smaller.  Add a new class to your project and paste the code shown below.  Compile.  Drop the new control from the top of the toolbox onto your form.  It is active in the designer, you should see the effect right away.

    using System;
    using System.Drawing;
    using System.Windows.Forms;

    public class FadeLabel : Label {
      private ColorAnimator mAnim = new ColorAnimator();
      private string mText = "";

      public FadeLabel() {
        mAnim.Change += new EventHandler(mAnim_Change);
      }
      public override Color ForeColor {
        get { return base.ForeColor; }
        set { base.ForeColor = value; mAnim.Color = value; }
      }
      public override string Text {
        get { return base.Text; }
        set { if (base.Text != "") mAnim.Begin(); mText = base.Text; base.Text = value; }
      }
      void mAnim_Change(object sender, EventArgs e) {
        Invalidate();
      }
      protected override void OnPaint(PaintEventArgs e) {
        string txt = mAnim.Fading ? mText : base.Text;
        using (Brush br = new SolidBrush(mAnim.Color))
          e.Graphics.DrawString(txt, this.Font, br, this.ClientRectangle);
      }

      internal class ColorAnimator : Timer {
        public event EventHandler Change;
        private const int cRate = 5;    // Tweak this
        private Color mColor;
        private int mValue = 255;
        private int mStep = -1;

        public Color Color {
          get { return Color.FromArgb(mValue, mColor); }
          set { mColor = value; }
        }
        public void Begin() {
          mValue = 255;
          mStep = -cRate;
          Interval = 16;
          Enabled = true;
        }
        public bool Fading {
          get { return Enabled && mStep < 0; }
        }
        protected override void OnTick(EventArgs e) {
          mValue += mStep;
          if (mValue <= 0) { mValue = 0; mStep = -mStep; }
          if (mValue >= 255) { mValue = 255; Enabled = false; }
          if (Change != null) Change(this, EventArgs.Empty);
        }
      }
    }
    Hans Passant.
    • Marked as answer by Xystus777 Friday, May 1, 2009 10:27 AM
    Thursday, April 30, 2009 9:34 PM
    Moderator
  • Tergiver,

    I must say, that code is quite impressive. I love it! It's pretty sweet and should work perfect for me!

    nobugz,

    You also have a great code, I like them both!
    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 11:04 AM
  • Tergiver, one more question, sorry!

    I definitely figured out how to animate the text in a control.

    I also have a spot where I disable a button altogether, then re-enable it later. Is there a way to make it fade when I disable it, and fade back in when I re-enable it? I'm not sure how to do that with your class. Thanks bud!


    Tergiver,

    Also, is there a way to do multiple animations at the SAME time? That would be VERY convenient. Any help on this? Thanks!
    Xystus777
    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 11:22 AM
  • Yes, you can add as many animations as you want. That sample calls AnimationManager.Clear before adding a new animation, but you don't have to. It will run all animations concurrently. In my newer code I provided a way to tag animations (with an object) so that you could cancel specific animations easily, but that code is not posted here.

    As for fading a button, that's going to be problematic I think. You might be able to animate the BackColor and ForeColor properties of the button to morph them into the the parent Form's BackColor, but there may be issues. If you first disable the button, I think you'll no longer have the button displaying itself using those properties. But if you don't first disable the button, the user could click or even hover the button and mess things up. I'll play with this some.

    It sounds to me like you would be better off with WPF. It has extensive animation support and is better suited to doing this kind of stuff because it tosses the system controls out the window and does all its own drawing. I'm just throwing that out there, maybe it's too late for you to switch your current project, but in the future, know that WPF is generally the better tool for doing fancy user interfaces.

    Friday, May 1, 2009 12:57 PM
  • Well, it's probably too late to move to WPF. I really love your code.

    I think it'd be great if you could email me the whole thing to Andrew.Davis@bdk.com? As for fading the button, don't worry about it, not a concern. My main concern was to fade the text.

    So, to run animations at the same time, I'm a little confused about how to do this, could you post a small example that just does two minor animations at the same time? Thanks!

    Feel free to respond to this post, or email me at Andrew.Davis@bdk.com to speak with me directly. Thanks!

    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 1:01 PM
  • This might be possible if you were to create a custom control that was transparent, but which could fetch the rectangle underneath itself and perform some alpha blending to fade itself in and out (thus hiding/showing the control underneath it). This is non-trivial at best. Like I said, Windows controls just were not designed for this kind of thing.
    Friday, May 1, 2009 1:18 PM
  • I'm not concerned about the button anymore.

    But didn't you say that there was a way for me to run multiple animations simultaneously? For example, fade 3 labels at the same time, instead one each one by one?

    Could you please email me your full code for this too? Andrew.Davis@bdk.com
    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 1:20 PM
  • I will email you the code, though I am not home for the next eight or nine hours (home is where it's at).

    Concurrent animations are easy, just add them to the AnimationManager, but don't call Clear as that will cancel *all* running animations.

    The confusing bit might be the AnimationSequence. That object is an animation, but it runs a sequence of animations back to back instead of concurrently (like fade out, change text, fade in, as in the sample). If you had two sequences you wanted to run at the same time:

    // Create a sequence
    AnimationSequence seq1 = new AnimationSequence(
        new PropertyAnimation("ForeColor", label1, label1.BackColor, TimeSpan.FromSeconds(1), new ColorAnimation())
       ,new PropertyAnimation("Text", label1, "New Label1 Text")
       ,new PropertyAnimation("ForeColor, label1, SystemColors.ControlText, TimeSpan.FromSeconds(1), new ColorAnimation())
       );
    // Start it running
    animationManager1.Add(seq1);

    // Create another sequence
    AnimationSequence seq2 = new AnimationSequence(
        new PropertyAnimation("ForeColor", label2, label2.BackColor, TimeSpan.FromSeconds(1), new ColorAnimation())
       ,new PropertyAnimation("Text", label2, "New Label2 Text")
       ,new PropertyAnimation("ForeColor, label2, SystemColors.ControlText, TimeSpan.FromSeconds(1), new ColorAnimation())
       );

    // Start it running
    animationManager1.Add(seq2);

    The latest code and it's sample will show a better way to do this because it gives you a way to tag animations so you don't have to call Clear, you can just remove a specific one (to restart just that one).
    Friday, May 1, 2009 1:38 PM
  • Alright sweet, I'll give that a shot. Thanks man :) I really appreciate this.

    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 1:40 PM
  • Maybe I should explain why you need to stop running animations at all:

    If you run the sample (above) and click button1, the animation plays. If, before the animation has completed, you click button 1 again, that sample calls Clear, stopping all running animations, then starting a new animation.

    If you remove the call to Clear and try this, both the first click animation and the second click animation will run concurrently. However, both of those animations are messing with the same control property. They will interfere with each other and the result will not look right.


    The new code allows you to provide an object tag so that you can clear only select animations. Instead of calling Clear which blindly stops all animations running, you do like this:

    // Remove any animation previously tagged with label1 (only if it is running, this does nothing if the previous one has run its course)
    animationManager1.Remove(label1);
    // Start a new animation on label1 and tag that animation as label1
    animationManager1.Add(... , label1);

    Notice that I use the control itself as a tag for the animation. You can use any object, but in this case the label control itself makes an ideal tag. We could use a string instead if that is more suitable:

    animationManager1.Remove("Label1 Fade");
    animationManager1.Add(... , "Label1 Fade");


    Btw, you can actually achieve the same thing using the code posted above. There is a Remove method, but it requires that you hold onto a reference to the animation object to remove:

    Animation button1Animation = null; // declared in the form
    void button1_Clicked(...)
    {
        if (button1Animation != null)
            animationManager1.Remove(button1Animation);
        button1Animation = new ... ;
        animationManager1.Add(button1Animation);
    }

    This works, but is not has handy as the tagging method that the newer code uses. Since the animation object provides no way to signal the outside world that it has run its course, holding a reference to it will persist it, either until a new animation is created, or until the end of the Form's lifetime. It's not a heavy-weight object, but still, that's not very efficient.
    Friday, May 1, 2009 2:10 PM
  • Is it possible to fade the text in a readonly textbox? And also a normal textbox?

    In my program, the contents of the textbox itself changes frequently and I would like it to fade-out, change text, and fade-in just like I can do with the labels. But it doesn't appear to work with a textbox.
    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 5:17 PM
  • It wont work if the TextBox's ReadOnly property is true or if the Enabled property is false. The reason is that the control does not use the ForeColor property (or any accessible property) when in those states. This again, a limitation of Windows controls. You'd have to write your own text box control (sub-classing TextBox will not be sufficient), or I would again have to refer you to WPF where all aspects of the drawing code can be manipulated.

    You can do it with a TextBox that does not have these properties (as you can see in the sample--button3), but to protect the user from messing with the contents during animation, you'll have to erect a transparent control on top of the text box for the duration of the animation. This can be achieved by putting the transparent control's Enabled property in the sequence:

    new AnimationSequence(
        new PropertyAnimation("Enabled", transparentPanel, true)
       , ... Add the fade out, change text, fade in animations
       , new PropertyAnimation("Enabled", transparentPanel, false)
       );
    Friday, May 1, 2009 6:08 PM
  • Another thing you could do is skip the barrier, but repeat the text change at the end of the sequence:

    new AnimationSequence(
        new PropertyAnimation("ForeColor", textBox1, textBox1.BackColor, TimeSpan.FromSeconds(1), new ColorAnimation())
       ,new PropertyAnimation("Text", textBox1, "New Text")
       ,new PropertyAnimation("ForeColor, textBox1, SystemColors.ControlText, TimeSpan.FromSeconds(1), new ColorAnimation())
       ,new PropertyAnimation("Text", textBox1, "New Text")
       );

    That way, even if the user changes the text in the text box during the animation, those changes will be overridden by the final animation in the sequence.
    Friday, May 1, 2009 6:24 PM
  • Well, I kind of need the animation to work on a readonly text box, but that doesn't work. So I'll just have to do something else with my program. Maybe I will make it a label then, hmm...What I might do is have a textbox that's not visible, but in the same location as a label. So when the user doubleclicks the label, the label will "hide" and the textbox will "show" in it's place. That "could" work.
    Kettering University Co-Op davi9267@kettering.edu
    Friday, May 1, 2009 6:27 PM
  • Well, what I did for my textbox. Is...I have it "readonly" but when it's time for the user to edit it. I turn readonly off, and make the BackColor set to SystemColors.Control; So it still "looks" like it's readonly haha. It then fades, then I turn Readonly back on. it's great.
    Kettering University Co-Op davi9267@kettering.edu
    Monday, May 4, 2009 12:40 PM
  • I found a code to fade out a text and adjusted it a little bit to use it in my applications, use the Fade() method in the code bellow:

    #region Fade
            /// <summary>
            /// Declarations
            /// </summary>
            public Control Cont;
            int ialpha;
            int ired;
            int igreen;
            int iblue;
            int lalpha;
            int lred;
            int lgreen;
            int lblue;
            bool lanegative;
            bool lrnegative;
            bool lgnegative;
            bool lbnegative;
    
            public void Fade(Control cont, Color col)
            {
                Cont = cont;
                lanegative = true; //Set everything to true first then check
                lrnegative = true;
                lgnegative = true;
                lbnegative = true;
    
                cont.ForeColor = col; //Set the color for the label
    
                ialpha = this.BackColor.A; //Capture the panels background color alpha this can be changed to the form or whatever control is being used
                ired = this.BackColor.R; //Capture the panels background color red this can be changed to the form or whatever control is being used
                igreen = this.BackColor.G; //Capture the panels background color green this can be changed to the form or whatever control is being used
                iblue = this.BackColor.B; //Capture the panels background color blue this can be changed to the form or whatever control is being used
    
                lalpha = cont.ForeColor.A; //Capture the labels foreground color alpha
                lred = cont.ForeColor.R; //Capture the labels foreground color red
                lgreen = cont.ForeColor.G; //Capture the labels foreground color green
                lblue = cont.ForeColor.B; //Capture the labels foreground color blue
    
                if (ialpha >= lalpha) //Check to see if the value should be increased or decreased
                    lanegative = false;
                if (ired >= lred) //Check to see if the value should be increased or decreased
                    lrnegative = false;
                if (igreen >= lgreen) //Check to see if the value should be increased or decreased
                    lgnegative = false;
                if (iblue >= lblue) //Check to see if the value should be increased or decreased
                    lbnegative = false;
    
                FadeWorkTimer.Enabled = true; //Start timer
                FadeWorkTimer.Start();
            }
    
            private void FadeWorkTimer_Tick(object sender, EventArgs e)
            {
                if (lalpha != ialpha || lred != ired || lblue != iblue || lgreen != igreen)
                {
                    if (lanegative == true) //Set alpha will determine to increase or decrease alpha
                    {
                        if (lalpha > ialpha)
                        {
                            lalpha -= 2;
                        }
                    }
                    else
                    {
                        if (lalpha < ialpha)
                        {
                            lalpha += 2;
                        }
                    }
    
                    if (lrnegative == true) //Set red will determine to increase or decrease red
                    {
                        if (lred > ired)
                        {
                            lred -= 2;
                        }
                    }
                    else
                    {
                        if (lred < ired)
                        {
                            lred += 2;
                        }
                    }
    
                    if (lgnegative == true) //Set green will determine to increase or decrease green
                    {
                        if (lgreen > igreen)
                        {
                            lgreen -= 2;
                        }
                    }
                    else
                    {
                        if (lgreen < igreen)
                        {
                            lgreen += 2;
                        }
                    }
    
                    if (lbnegative == true) //Set blue will determine to increase or decrease blue
                    {
                        if (lblue > iblue)
                        {
                            lblue -= 2;
                        }
                    }
                    else
                    {
                        if (lblue < iblue)
                        {
                            lblue += 2;
                        }
                    }
    
                    //this.label1.Text = lalpha + "," + lred + "," + lgreen + "," + lblue; //See what is happening
    
                    Cont.ForeColor = System.Drawing.Color.FromArgb(lalpha, lred, lgreen, lblue); //Set the labels current color ex: fading effect
    
                    //this.Refresh();
                }
                else
                {
                    FadeWorkTimer.Enabled = false;
                    FadeWorkTimer.Stop();
                }
            }
            #endregion
     You can invert the calculations to get fade in. However, I didn't test the fade in as I didn't need it so far but you can give it a try. BTW you must make a timer and name it FadeWorkTimer and set the interval to 1ms and the tick event to FadeWorkTimer_Tick.

    Thursday, November 19, 2009 7:54 PM