Advanced C# splash screen
I once posted about how to make a splash screen with C#; yet it’s still a rectangle. While it’s true that you can replace my picture with something that have curvy edges, you’ll have aliased edges i.e. crooked and crude pixels around the edges.
There’s a solution: alpha-blend the picture and you can see through the form; pixels with alpha value of neither 0 or 255 will be mixed appropriately with the background.
To achieve this, you have to write your custom painting function with windows API, specifically this one
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)] public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);
Which can be a daunting task. Luckily we have someone from Visual C# kicks did the hard work and released the source for public use! But that’s not the end of my story just yet. I wanted the splash screen to fades in and out. The timing part was easy, just a timer and several more lines of code and it’s done.
enum Phases { FadeIn, Hold, FadeOut }; Phases CurrentPhase = Phases.FadeIn; int FadeInStep = 0; const int FadeInLast = 20; int HoldStep = 0; const int HoldLast = 80; int FadeOutStep = 20; const int FadeOutLast = 0; const int OpacityMultiplier = 5; private void timerFade_Tick(object sender, EventArgs e) { if (CurrentPhase == Phases.FadeIn) { if (FadeInStep < FadeInLast) { FadeInStep++; this.Opacity = (OpacityMultiplier * FadeInStep) / 100.0; } else CurrentPhase = Phases.Hold; } else if (CurrentPhase == Phases.Hold) { if (HoldStep < HoldLast) { HoldStep++; this.UpdateFormDisplay(this.BackgroundImage); } else CurrentPhase = Phases.FadeOut; } else if (CurrentPhase == Phases.FadeOut) { { FadeOutStep--; this.Opacity = (OpacityMultiplier * FadeOutStep) / 100.0; } else this.Close(); }
I have even altered the custom paint code to utilize the form’s transparency factor
//Set up blending options API.BLENDFUNCTION blend = new API.BLENDFUNCTION(); blend.BlendOp = API.AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = (byte)(255 * this.Opacity); blend.AlphaFormat = API.AC_SRC_ALPHA; API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, this.BackColor.ToArgb(), ref blend, API.ULW_ALPHA);
But that didn’t work to my expectation! The form is fading in and out gradually but the alpha blending effect is lost.
After fiddling around, I figured out that the custom paint code somehow didn’t override all the necessary painting functions and the setter of this.Opacity call those functions every time it’s changed. Searching around the list of functions can be overridden I found nothing interesting
So I created a new variable and leave the form’s default Opacity alone. After all, if I have adjusted transparency myself in the custom paint function, who needs .NET’s alpha-blend-incapable render? The code above becomes:
double MyOpacity = 0; if (CurrentPhase == Phases.FadeIn) { if (FadeInStep < FadeInLast) { FadeInStep++; MyOpacity = (OpacityMultiplier * FadeInStep) / 100.0; this.UpdateFormDisplay(this.BackgroundImage); } else CurrentPhase = Phases.Hold; } else if (CurrentPhase == Phases.Hold) { if (HoldStep < HoldLast) { HoldStep++; this.UpdateFormDisplay(this.BackgroundImage); } else CurrentPhase = Phases.FadeOut; } else if (CurrentPhase == Phases.FadeOut) { if (FadeOutStep > FadeOutLast) { FadeOutStep--; MyOpacity = (OpacityMultiplier * FadeOutStep) / 100.0; this.UpdateFormDisplay(this.BackgroundImage); } else this.Close(); } }
//Set up blending options API.BLENDFUNCTION blend = new API.BLENDFUNCTION(); blend.BlendOp = API.AC_SRC_OVER; blend.BlendFlags = 0; blend.SourceConstantAlpha = (byte)(255 * MyOpacity); blend.AlphaFormat = API.AC_SRC_ALPHA; API.UpdateLayeredWindow(this.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, this.BackColor.ToArgb(), ref blend, API.ULW_ALPHA);
And VoilĂ !
You can get the project, source code and complied binary here.