Better algorithm to fade a winform
Asked Answered
A

7

8

While searching for code to fade a winform, I came across this page on the MSDN forum.

for (double i = 0; i < 1; i+=0.01)
{
    this.Opacity = i;
    Application.DoEvents();
    System.Threading.Thread.Sleep(0);
}

The for loop has a non-integer increment and, from a previous question I asked, that's not a good programming technique (due to inexact representation of most decimals).

I came up with this alternative.

for (double i = 0; i < 100; ++i)
{
    this.Opacity = i/100;
    Application.DoEvents();
    System.Threading.Thread.Sleep(0);
}

Which of these is more efficient?

If there's a better algorithm for fading a form, I'll be very glad if it is included.

Thanks.

Albumose answered 19/9, 2012 at 15:23 Comment(4)
@DarrenDavies on Win32, IIRC, the Sleep(0) was to return the thread's remaining time to the scheduler to give other thread a chance to run. Maybe on .NET this is the same. MSDN states: "If you specify 0 milliseconds, the thread will relinquish the remainder of its time slice but remain ready."Benzol
How about simply changing int to double?Benzol
A vb.net Single is "float" in C#, not int.Corner
You could check out the Windows API - codeproject.com/Articles/43058/…Carolacarolan
P
23

Forget timers (pun intended).

With Visual Studio 4.5 or higher, you can just await a task that is delayed. An advantage of this method is that it's asynchronous, unlike a thread Sleep or DoEvents loop, which blocks the application during the fade (and the other aforementioned DoEvents problems).

private async void FadeIn(Form o, int interval = 80) 
{
    //Object is not fully invisible. Fade it in
    while (o.Opacity < 1.0)
    {
        await Task.Delay(interval);
        o.Opacity += 0.05;
    }
    o.Opacity = 1; //make fully visible       
}

private async void FadeOut(Form o, int interval = 80)
{
    //Object is fully visible. Fade it out
    while (o.Opacity > 0.0)
    {
        await Task.Delay(interval);
        o.Opacity -= 0.05;
    }
    o.Opacity = 0; //make fully invisible       
}

Usage:

private void button1_Click(object sender, EventArgs e)
{
    FadeOut(this, 100);
}

You should check if the object is disposed before you apply any transparency to it. I used a form as the object, but you can pass any object that supports transparency as long as it's cast properly.

Plattdeutsch answered 7/11, 2014 at 17:9 Comment(4)
When I call FadeIn from a background worker completed event, the 'await' line causes a deadlock. How can I avoid this?Polacre
@NineTails have you tried wrapping the FadeIn method in a this.BeginInvoke(new Action(() => FadeIn(...))) ?Suffragette
@drake7707 Thanks. However I previously solved my issue with 'SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());'Polacre
I tried the method above in WPF and it works perfectly with no threading modifications (I did have to remove the references to Windows Forms, but that was easy-peasy). I just had to set the Window opacity to 0 first for the fade-in to work.Soraya
B
14

So, first off, application.DoEvents should be avoided unless you really know what you're doing and are sure that this is both an appropriate use of it, and that you are using it correctly. I'm fairly certain that neither is the case here.

Next, how are you controlling the speed of the fading? You're basically just letting the computer fade as quickly as it can and relying on the the inherent overhead of the operations (and background processes) to make it take longer. That's really not very good design. You're better off specifying how long the fade should take from the start so that it will be consistent between machines. You can use a Timer to execute code at the appropriate set intervals and ensure that the UI thread is not blocked for the duration of the fade (without using DoEvents).

Just modify the duration below to change how long the fade takes, and modify the steps to determine how "choppy" it is. I have it set to 100 because that's effectively what your code was doing before. In reality, you probably don't need that many and you can just lower to just before it starts getting choppy. (The lower the steps the better it will perform.)

Additionally, you shouldn't be so worried about performance for something like this. The fade is something that is going to need to be measured on the scale of about a second or not much less (for a human to be able to perceive it) and for any computer these days it can do so, so much more than this in a second it's not even funny. This will consume virtually no CPU in terms of computation over the course of a second, so trying to optimize it is most certainly micro-optimizing.

private void button1_Click(object sender, EventArgs e)
{
    int duration = 1000;//in milliseconds
    int steps = 100; 
    Timer timer = new Timer();
    timer.Interval = duration / steps;

    int currentStep = 0;
    timer.Tick += (arg1, arg2) =>
    {
        Opacity = ((double)currentStep) / steps;
        currentStep++;

        if (currentStep >= steps)
        {
            timer.Stop();
            timer.Dispose();
        }
    };

    timer.Start();
}
Birthstone answered 19/9, 2012 at 16:29 Comment(2)
This is a savvy comment, and I specially like the fact that you're insisting that "DoEvents should be avoided"... IT REALLY SHOULD. I also do something very similar, but you are missing something important: You should stop the timer at some point!!! Within the Tick handler, include the check if (Opacity >= 1.0) { timer.Stop(); timer.Dispose(); }Circumfluent
@khovanskiiªn Thanks, I totally forgot to stop the timer; edited in.Birthstone
T
4

Example Form Fading In

I wrote a class specifically for fading forms in and out. It even supports ShowDialog and DialogResults.

I've expanded on it as I've needed new features, and am open to suggestions. You can take a look here:

https://gist.github.com/nathan-fiscaletti/3c0514862fe88b5664b10444e1098778

Example Usage

private void Form1_Shown(object sender, EventArgs e)
{
    Fader.FadeIn(this, Fader.FadeSpeed.Slower);
}
Terrier answered 28/3, 2017 at 10:4 Comment(0)
I
3
for (double i = 0; i < 1; i+=0.01)
{
    this.Opacity = i;
    Application.DoEvents();
    System.Threading.Thread.Sleep(0);
}

is more efficient as the number of floating point divisions are more machine-expensive than compared to floating point additions(which do not affect vm-flags). That said, you could reduce the number of iterations by 1/2(that is change step to i+=0.02). 1% opacity reduction is NOT noticeable by the human brain and will be less expensive too, speeding it up almost 100% more.

EDIT:

for(int i = 0; i < 50; i++){
     this.Opacity = i * 0.02;
     Application.DoEvents();
     System.Threading.Thread.Sleep(0);
}
Iain answered 19/9, 2012 at 15:30 Comment(0)
M
2

I applied the approach of Victor Stoddard to a splashScreen. I used it in the Form_Load event to fadeIn and FormClosing event to fadeOut. NOTE: I had to set the form's opacity to 0 before call the fadeIn method.

Here you can see the order of events rised by a winform (lifecycle): https://msdn.microsoft.com/en-us/library/86faxx0d(v=vs.110).aspx

private void Splash_Load(object sender, EventArgs e)
{
    this.Opacity = 0.0;
    FadeIn(this, 70);
}


private void Splash_FormClosing(object sender, FormClosingEventArgs e)
{
    FadeOut(this, 30);
}

private async void FadeIn(Form o, int interval = 80)
{
    //Object is not fully invisible. Fade it in
    while (o.Opacity < 1.0)
    {
        await Task.Delay(interval);
        o.Opacity += 0.05;
    }
    o.Opacity = 1; //make fully visible       
}

private async void FadeOut(Form o, int interval = 80)
{
    //Object is fully visible. Fade it out
    while (o.Opacity > 0.0)
    {
        await Task.Delay(interval);
        o.Opacity -= 0.05;
    }
    o.Opacity = 0; //make fully invisible       
}
Malisamalison answered 11/11, 2016 at 18:17 Comment(0)
O
1

In the past, I've used AnimateWindow to fade in/out a generated form that blanks over my entire application in SystemColor.WindowColor.

This neat little trick gives the effect of hiding/swapping/showing screens in a wizard like interface. I've not done this sort of thing for a while, but I used P/Invoke in VB and ran the API in a thread of its own.

I know your question is in C#, but it's roughly the same. Here's some lovely VB I've dug out and haven't looked at since 2006! Obviously it would be easy to adapt this to fade your own form in and out.

<DllImport("user32.dll")> _
Public Shared Function AnimateWindow(ByVal hwnd As IntPtr, ByVal dwTime As Integer, ByVal dwFlags As AnimateStyles) As Boolean
End Function

Public Enum AnimateStyles As Integer
    Slide = 262144
    Activate = 131072
    Blend = 524288
    Hide = 65536
    Center = 16
    HOR_Positive = 1
    HOR_Negative = 2
    VER_Positive = 4
    VER_Negative = 8
End Enum

Private m_CoverUp As Form

Private Sub StartFade()
    m_CoverUp = New Form()
    With m_CoverUp
        .Location = Me.PointToScreen(Me.pnlMain.Location)
        .Size = Me.pnlMain.Size
        .FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
        .BackColor = Drawing.SystemColors.Control
        .Visible = False
        .ShowInTaskbar = False
        .StartPosition = System.Windows.Forms.FormStartPosition.Manual
    End With
    AnimateWindow(m_CoverUp.Handle, 100, AnimateStyles.Blend) 'Blocks
    Invoke(New MethodInvoker(AddressOf ShowPage))
End Sub

Private Sub EndFade()
    AnimateWindow(m_CoverUp.Handle, 100, AnimateStyles.Blend Or AnimateStyles.Hide)
    m_CoverUp.Close()
    m_CoverUp = Nothing
End Sub
Ostrom answered 19/9, 2012 at 15:52 Comment(2)
This is Visual Basic.Raskin
The question was in C#Cumbrous
G
1

Victor Stoddard is close, but I found that the fade was more pleasing if it started the opacity increase fast and slowed as it approached full opacity of 1. Here's a slight modification to his code:

    using System.Threading.Tasks;
    using System.Windows.Forms;

    public partial class EaseInForm : Form
    {
        public EaseInForm()
        {
            InitializeComponent();
            this.Opacity = 0;
        }

        private async void Form_Load(object sender, EventArgs e)
        {
            while (this.Opacity < 1.0)
            {
                var percent = (int)(this.Opacity * 100);
                await Task.Delay(percent);
                this.Opacity += 0.04;
            }
        }
    }
Gaylenegayler answered 30/6, 2020 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.