Click here to Skip to main content
Click here to Skip to main content

Fading Forms In and Out

By , 13 Aug 2007
 
Screenshot - FadeForm.jpg

Introduction

Disclaimer: I am not the first to make a form fader, but I couldn't find one that did exactly what I wanted and so I created this.

I have had need in the past to cause my forms to perform fade transitions from one opacity to another. It was usually on load, close and window state changes. I finally decided to bring it all together in a nice, extendable Form. The FadeForm...

  • fades in on open.
  • fades out on close.
  • partially fades out on focus lost.
  • fades in on focus.
  • fades out on minimize.
  • fades in on restore.
  • must not annoy the user. :-) A form fader definitely has that potential.

Using the Code

To use FadeForm, just extend it instead of Form and you are ready to go.

public class Form1 : FadeForm
{
     ...
}

It is defaulted to use the fade-in from nothing on open and out to nothing on close. It will also fade to 85% opacity when not the active window. You can set it to whatever you want, however.

//This would set the form with its default values.
this.ActiveOpacity=1;
this.InactiveOpacity=.85;
this.MinimizedOpacity=0;

You may, from time to time, want to disable the fade effect.

this.DisableFade(); //Turn off fade effects
this.EnableFadeDefaults(); //Turns on fade effects

You can also change the transition time.

this.FadeTime=1; //1 sec transition

You can also do a one-time fade to any value.

this.TargetOpacity=.25; //Fades the form to 25% opacity

Points of Interest

The opening and focus change events were easy to deal with. It was appropriate to use the built-in event listeners.

public FadeForm()
{
    ...
    this.timer.Tick += new System.EventHandler(this.timer_Tick);
    
    this.Deactivate += new System.EventHandler(this.FadeForm_Deactivate);
    this.Activated += new System.EventHandler(this.FadeForm_Activated);
    this.Load += new System.EventHandler(this.FadeForm_Load);
}

private void FadeForm_Load(object sender, EventArgs e)
{
   this.Opacity = minimizedOpacity;
   this.TargetOpacity = activeOpacity;
}

private void FadeForm_Deactivate(object sender, EventArgs e)
{
    this.TargetOpacity = inactiveOpacity;
}

private void FadeForm_Activated(object sender, EventArgs e)
{
    this.TargetOpacity = activeOpacity;
}

The minimize and close events where a little trickier because the actions had to be postponed until the fade transition was complete. I had to override WndProc in order to catch the request for those actions and postpone the action until the transition was done.

private const int WM_SYSCOMMAND = 0x112;
private const int WM_COMMAND = 0x111;
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
private const int SC_CLOSE = 0xF060; 

// Intercepts WindowMessages before they are processed.
protected override void WndProc(ref Message m)
{
    if (m.Msg==WM_SYSCOMMAND||m.Msg == WM_COMMAND) 
    {
        //Fade to zero on minimze
        if (m.WParam == (IntPtr)SC_MINIMIZE) 
        { 
            heldMessage = m;
            this.TargetOpacity = minimizedOpacity;
            return;
         }

         //Fade in if the window is restored from the taskbar
         else if (m.WParam == (IntPtr)SC_RESTORE 
           && this.WindowState == FormWindowState.Minimized) 
         { 
             base.WndProc(ref m);  
             this.TargetOpacity = activeOpacity;
             return;
         }

         //Fade out if the window is closed.
         else if (m.WParam == (IntPtr)SC_CLOSE) 
         { 
             heldMessage = m; 
             this.TargetOpacity = minimizedOpacity;
             return;
         }
     }
     base.WndProc(ref m);
}

Once that was done, all I had to do was perform the transitions.

//Performs fade increment.
private void timer_Tick(object sender, EventArgs e)
{
    double fadeChangePerTick = timer.Interval * 1.0 / 1000 / fadeTime;

    //Check to see if it is time to stop the timer
    if (Math.Abs(targetOpacity - this.Opacity) < fadeChangePerTick)
    {
        //There is an ugly black flash if you set the Opacity to 1.0
        if (targetOpacity == 1) this.Opacity = .999;
        else this.Opacity = targetOpacity;
        
        //Process held Windows Message.
        base.WndProc(ref heldMessage);
        heldMessage = new Message();
        
        //Stop the timer to save processor.
        timer.Stop();
    }
    else if (targetOpacity > this.Opacity) this.Opacity += fadeChangePerTick;
    else if (targetOpacity < this.Opacity) this.Opacity -= fadeChangePerTick;
}

It is interesting to notice that the opacity is never actually 1. That was a hack on my part to keep the window from flashing black. I am not sure what the cause of this is, but the fix was easy, so I may never know.

You can see that I stop the timer after I reach my target opacity. You may wonder how it gets started. Every time I set TargetOpacity, the set method starts the timer. There's no sense running the timer and wasting processor power when you don't need it.

private double TargetOpacity
{
    set
    {
        targetOpacity = value;
         if (!timer.Enabled) timer.Start();
    }
    get 
    { 
        return targetOpacity; 
    }
}

I think I made it about as easy as possible. I always like to adhere to the KISS principle: Keep it simple, stupid.

Thoughts

I plan to add a Notify(int n) method that will oscillate the opacity n times in order to get the user's attention. I also think it would be nice to give a choice between mouseover-based and focus-based transitions.

History

  • 13 August, 2007 -- Original version posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nicholas Seward
Instructor / Trainer
United States United States
Member
I am a mechanical engineer that works as a high school math teacher in Mulberry, Arkansas. I use programming as one way to keep my mind sharp.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralGeneral Commentsmemberchris17522 Aug '07 - 2:25 
I really like this code. I think it is pretty cool.
 
Comments...
1. I think you should make DisableFade a boolean property which doesn't change the other properties.
2. On all your properties you might want to include the category attribute, description attribute and refreshproperties attribute in the System.ComponentModel namespace. This way when the user is using the designer of a form derived from your class the descriptions will show in the properties grid.
3. Instead of using private void FadeForm_Load(object sender, EventArgs e) try using protected override void OnLoad(EventArgs e) { base.OnLoad(e); }
4. Look at using OnActivated and OnDeactivate.
5. FadeForm doesn't have a designer file so you can remove the "partial" from the class declaration.
6. You can remove the "using System.Drawing" namespace.
7. Running code anaysis on this file throws off some warnings but not too many.
 
Chris
GeneralRe: General Comments PinmemberNicholas Seward26 Aug '07 - 7:05 
Man, I love the time that you took to review my code. I can tell that posting on the project is going to make me a much better programmer.
 
1) Done. At least the next version will have it.
2) Done. I always wondered how to group props so now I know.
3) Curious why you suggest the switch. It seems like just a different way to skin the cat. Let me know.
4) I have explored OnActivated and OnDeactivated but there was a problem with them early on that I didn't document and promptly forgot. I am curious to go back and check it out but my job is too hectic now to even think about it.
5) Done. Easy.
6) Done.
7) I am a Noob so how do I run an analysis. I don't want any dust bunnies.
 
Thanks again for you input.
 
Nicholas Seward

GeneralRe: General Comments Pinmemberchris17527 Aug '07 - 1:13 
I am really interested in getting your updated version.
 
I guess the main reason for using OnActivated and OnDeactivated is for deriving classes. If you derive a class from FadeForm (I don’t know why anyone would ever do this) you can override the OnActivated and OnDeactivated and choose to run code before and/or after the derived classes OnActivated and OnDeactivated.
 
Also I am noticing while resizing the window there is "black" space. This is just a minor issue.
 
This is my new FadeForm.cs and may give you some ideas to incorporate in your code. It may also show something wrong with my code. Please note the [RefreshProperties(RefreshProperties.All)] attribute on the MinimizedOpacity property. Because the MinimizedOpacity property changes another property you would want to refresh that other property on the property grid in the design window. I am also not sure of all the properties that could use the ArgumentOutOfRangeException to validate the value coming into the property.
 
Have fun...
 
using System;
using System.ComponentModel;
using System.Windows.Forms;
 
namespace Test
{
   /// <summary>
   /// A form that fades in and out.
   /// </summary>
   public class FadeForm : Form
   {
      #region WindowsMessageCodes
      private const int WMSysCommand = 0x112;
      private const int WMCommand = 0x111;
      private const int SCMinimize = 0xF020;
      private const int SCRestore = 0xF120;
      private const int SCClose = 0xF060;
      #endregion
 
      #region Variables
      /// <summary>
      /// WindowsMessage that is being held until the end of a transition.
      /// </summary>
      private Message _HeldMessage = new Message();
 
      /// <summary>
      /// Timer to aid in fade effects.
      /// </summary>
      private System.Windows.Forms.Timer _Timer;
      #endregion
 
      #region Properties
      #region ActiveOpacity
      private double _ActiveOpacity = 1;
      /// <summary>
      /// Gets or sets the opacity that the form will transition to when the form gets focus which must be a positive number.
      /// </summary>
      /// <exception cref="ArgumentOutOfRangeException">Thrown if this property is not set to a positive value.</exception>
      [Category("Custom")]
      [Description("The opacity that the form will transition to when the form gets focus which must be a positive number.")]
      [RefreshProperties(RefreshProperties.All)]
      public double ActiveOpacity
      {
         get { return _ActiveOpacity; }
         set
         {
            if (value >= 0)
               _ActiveOpacity = value;
            else
               throw new ArgumentOutOfRangeException("value", Properties.Resources.ErrorActiveOpacity);
            
            if (ContainsFocus)
               TargetOpacity = _ActiveOpacity;
         }
      }
      #endregion
 
      #region FadeTime
      private double _FadeTime = 0.35;
      /// <summary>
      /// Gets or sets the time it takes to fade from 0 to 1 or the other way around.
      /// </summary>
      /// <exception cref="ArgumentOutOfRangeException">Thrown if this property is not set to a positive value between 0 and 1.</exception>
      [Category("Custom")]
      [Description("The time it takes to fade from 0 to 1 or the other way around.")]
      public double FadeTime
      {
         get { return _FadeTime; }
         set
         {
            if (value > 0 && value < 1)
               _FadeTime = value;
            else
               throw new ArgumentOutOfRangeException("value", Properties.Resources.ErrorFadeTime);
         }
      }
      #endregion
 
      #region InactiveOpacity
      private double _InactiveOpacity = .85;
      /// <summary>
      /// Gets or sets the opacity that the form will transition to when the form doesn't have focus.
      /// </summary>
      /// <exception cref="ArgumentOutOfRangeException">Thrown if this property is not set to a value greater than or equal to 0.</exception>
      [Category("Custom")]
      [Description("The opacity that the form will transition to when the form doesn't have focus.")]
      [RefreshProperties(RefreshProperties.All)]
      public double InactiveOpacity
      {
         get { return _InactiveOpacity; }
         set
         {
            if (value >= 0)
               _InactiveOpacity = value;
            else
               throw new ArgumentOutOfRangeException("value", Properties.Resources.ErrorInactiveOpacity);
            
            if (!ContainsFocus && WindowState != FormWindowState.Minimized)
               TargetOpacity = _InactiveOpacity;
         }
      }
      #endregion
 
      #region MinimizedOpacity
      private double _MinimizedOpacity;
      /// <summary>
      /// Gets or sets the opacity that the form will transition to when the form is minimized.
      /// </summary>
      /// <exception cref="ArgumentOutOfRangeException">Thown if this property is not set to a value greater than or equal to 0.</exception>
      [Category("Custom")]
      [Description("The opacity that the form will transition to when the form is minimized.")]
      [RefreshProperties(RefreshProperties.All)]
      public double MinimizedOpacity
      {
         get { return _MinimizedOpacity; }
         set
         {
            if (value >= 0)
               _MinimizedOpacity = value;
            else
               throw new ArgumentOutOfRangeException("value", Properties.Resources.ErrorMinimizedOpacity);
            
            if (!ContainsFocus && WindowState != FormWindowState.Minimized)
               TargetOpacity = InactiveOpacity;
         }
      }
      #endregion
 
      #region TargetOpacity
      private double _TargetOpacity;
      /// <summary>
      /// Gets or sets the opacity the form is transitioning to.
      /// </summary>
      [Category("Custom")]
      [Description("The opacity the form is transitioning to.")]
      public double TargetOpacity
      {
         get { return _TargetOpacity; }
         set
         {
            _TargetOpacity = value;
            if (!_Timer.Enabled)
               _Timer.Start();
         }
      }
      #endregion
      #endregion
 
      #region Constructor
      /// <summary>
      /// Constructor
      /// </summary>
      [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Mobility", "CA1601:DoNotUseTimersThatPreventPowerStateChanges", Scope = "member", Target = "FadeForm.FadeForm..ctor()")]
      public FadeForm()
      {
         this._Timer = new System.Windows.Forms.Timer();
         this.SuspendLayout();
         this._Timer.Interval = 25;
         this._Timer.Tick += new System.EventHandler(this._Timer_Tick);
      }
      #endregion
 
      #region DisableFade
      /// <summary>
      /// Turns off opacitiy fading.
      /// </summary>
      public void DisableFade()
      {
         ActiveOpacity = 1;
         InactiveOpacity = 1;
         MinimizedOpacity = 1;
      }
      #endregion
 
      #region EnableFadeDefaults
      /// <summary>
      /// Turns on opacitiy fading.
      /// </summary>
      public void EnableFadeDefaults()
      {
         ActiveOpacity = 1;
         InactiveOpacity = .85;
         MinimizedOpacity = 0;
         FadeTime = .35;
      }
      #endregion
 
      #region WndProc
      /// <summary>
      /// Intercepts WindowMessages before they are processed.
      /// </summary>
      /// <param name="m">Windows Message</param>
      [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, Flags = System.Security.Permissions.SecurityPermissionFlag.UnmanagedCode)]
      protected override void WndProc(ref Message m)
      {
         if (m.Msg == WMSysCommand || m.Msg == WMCommand)
         {
            //Fade to zero on minimze
            if (m.WParam == (IntPtr)SCMinimize)
            {
               if (_HeldMessage.WParam != (IntPtr)SCMinimize)
               {
                  _HeldMessage = m;
                  TargetOpacity = MinimizedOpacity;
               }
               else
               {
                  _HeldMessage = new Message();
                  TargetOpacity = ActiveOpacity;
               }
               return;
            }
 
           //Fade in if the window is restored from the taskbar
            else if (m.WParam == (IntPtr)SCRestore
                && this.WindowState == FormWindowState.Minimized)
            {
               base.WndProc(ref m);
               TargetOpacity = ActiveOpacity;
               return;
            }
 
           //Fade out if the window is closed.
            else if (m.WParam == (IntPtr)SCClose)
            {
               _HeldMessage = m;
               TargetOpacity = MinimizedOpacity;
               return;
            }
         }
 
         base.WndProc(ref m);
      }
      #endregion
 
      #region _Timer
      /// <summary>
      /// Called when the timer ticks.
      /// </summary>
      /// <param name="sender">Unused.</param>
      /// <param name="e">Unused.</param>
      private void _Timer_Tick(object sender, EventArgs e)
      {
         //Performs fade increment.
         double fadeChangePerTick = _Timer.Interval * 1.0 / 1000 / FadeTime;
 
         //Check to see if it is time to stop the timer
         if (Math.Abs(TargetOpacity - this.Opacity) < fadeChangePerTick)
         {
            //There is an ugly black flash if you set the Opacity to 1.0
            if (TargetOpacity == 1) this.Opacity = .999;
            else this.Opacity = TargetOpacity;
            //Process held Windows Message.
            base.WndProc(ref _HeldMessage);
            _HeldMessage = new Message();
            //Stop the timer to save processor.
            _Timer.Stop();
         }
         else if (TargetOpacity > this.Opacity) this.Opacity += fadeChangePerTick;
         else if (TargetOpacity < this.Opacity) this.Opacity -= fadeChangePerTick;
      }
      #endregion
 
      #region Activated
      /// <summary>
      /// Called when the form is activated.
      /// </summary>
      /// <param name="e">Unused.</param>
      protected override void OnActivated(EventArgs e)
      {
         base.OnActivated(e);
 
         //Fade in the form when it gaines focus.
         TargetOpacity = ActiveOpacity;
      }
      #endregion
 
      #region OnDeactivate
      /// <summary>
      /// Called when the form is deactivating.
      /// </summary>
      /// <param name="e">Unused.</param>
      protected override void OnDeactivate(EventArgs e)
      {
         base.OnDeactivate(e);
 
         //Fade out the form when it losses focus.
         this.TargetOpacity = InactiveOpacity;
      }
      #endregion
 
      #region OnLoad
      /// <summary>
      /// Called when the form is loading.
      /// </summary>
      /// <param name="e">Unused.</param>
      protected override void OnLoad(EventArgs e)
      {
         base.OnLoad(e);
         
         //Causes the form to fade in.
         this.Opacity = MinimizedOpacity;
         TargetOpacity = ActiveOpacity;
      }
      #endregion
   }
}

 
Chris
GeneralRe: General Comments PinmemberNicholas Seward27 Aug '07 - 4:46 
chris175 wrote:
Also I am noticing while resizing the window there is "black" space. This is just a minor issue.

 
I didn't notice that before. I did a little check and all forms with the opacity set below 1 display this behavior. I have a few ideas on how to fix this but none of them are pretty. The best idea I have is to somehow synchronize the resizing and the painting. Hold off on painting while the size is changing but once the size is stable send a refresh. It could be easy but something tells me that nothing is every easy. My guess is that it will deserve its own article.
 
Nicholas Seward

GeneralRe: General Comments Pinmemberchris17527 Aug '07 - 8:44 
I found out a few things.
 
First issue:
When placing controls on a form derived from FadeForm and setting the docking control they would not dock right. To fix this I removed the "this.SuspendLayout();" in the FadeForms constructor. I really dont know why it was in there.
 
Second issue:
I fixed the "black" space. In your code I see that you fixed it by setting the Opacity to .999. This makes sure the form will not flash black when the form looses focus.
 
Now for the resizing "black" space issue. In the form that is derived from FadeForm there is sometimes an Opacity property that gets placed in the .Designer.cs file which is set to .999. This property needs to be removed. How does it get there you ask? When messing with a form, the Visual Studio Designer actually executes the Constructor and OnLoad function of your form and base forms to see what needs to be in the .Designer.cs file. So in our case the FadeForm Constructor is called, then the OnLoad, then TargetOpacity gets set, then because the timer is not enabled it starts, which then calls the _Timer_Tick function and read this "this.Opacity = .999;". There is a simple fix. Right after "base.OnLoad(e);" place "if (DesignMode) return;". After the contructor is called the forms DesignMode variable is set. Then the OnLoad function is called. If DesignMode is true then the function is executing within a visual studios designer. If DesignMode is false then an application is executing the class.
 
Chris
GeneralRe: General Comments PinmemberNicholas Seward27 Aug '07 - 10:25 
I understand that the opacity gets changed to .999 but I wasn't worried about that. I was keeping it from every being 1 to stop the black flashing.
 
The only way to get rid of the black resizing issue is to have the opacity set to 1 but when you fade from 1 you get a black flash. So, you either get black flashes or black resizing.
 
FYI: Windows handles the painting of layered windows(opacity<1) and normal windows (opacity=1) differently. When you switch from one type to the other the transition is not handled seamlessly in windows.
 
Nicholas Seward

GeneralRe: General Comments Pinmemberchris17527 Aug '07 - 8:54 
I thought the black flash was fixed but I guess not. If you remove "if (TargetOpacity == 1) this.Opacity = .999;" then the black flash goes away.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 13 Aug 2007
Article Copyright 2007 by Nicholas Seward
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid