简介
回首以前,我到处寻找Splash Screen的例子,不过我找不到一个符合我的需求的例子。我需要一个简单的、容易复用的、可靠的、安全的——没有线程或子控件;并且,我希望它能够在启动的时候立即出现并停留在屏幕上,在必要的时候消失,当然你可以随时让它复现。我也看到有不少基于.NET下Win Form做的例子,而,这篇文章中,我将使用System.Runtime.InteropServices创建一个继承与控件类的最顶层窗口。我非常喜欢这个方案,因为,我发现它很容易定制。

一个Splash Screen控件 (闪屏窗体 C#)_ide

代码使用
添加SplashScreen.cs到你的程序中,到你的程序的Main函数中,一般来说这个函数在Program.cs文件里。

SplashScreen实现于Singleton模板类。通过一个私有的构造函数,和一个引用到已创建的实例的静态的成员完成...(设计模式上的概念,自己可以看看),最终目的,保证只有一个实例在同时运行。

调用方法

// program.cs

...
using SplashScreenControl;namespace WindowsApplication1
{
    static class Program
    {
        /// 
        /// The main entry point for the application.
        /// 
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault( false );

            // the following demonstrates how to get hold of the 
            // instance object and change control properties
            SplashScreen.Instance.Font = 
                new System.Drawing.Font( "Verdana", 11.75F, 
                System.Drawing.FontStyle.Regular, 
                System.Drawing.GraphicsUnit.Point, ((byte)(0)) );

            // begin displaying the splash screen before running the 
            // application form
            SplashScreen.SetBackgroundImage( 
                WindowsApplication1.Resources.splashbg );
            SplashScreen.SetTitleString( "SplashScreen Demo" );
            SplashScreen.BeginDisplay();

            // run the application
            Application.Run( new Form1() );
        }
    }
}

SplashScreen类原理
该类重写了CreateParams属性,以添加WS_EX_TOPMOST属性,这会使得窗口一直在最上方。同时,还增设了WS_EX_TOOLWINDOW,这样窗口就不会出现在任务栏,并且不会被你的Alt+Tab窗口切换看到。

// splashscreen.cs

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= WS_EX_TOOLWINDOW| WS_EX_TOPMOST;
        cp.Parent = IntPtr.Zero;
        return cp;
    }
}

然后,重写OnHandleCreated,设置其父窗口为你的桌面。

// splashscreen.cs

protected override void OnHandleCreated(EventArgs e)
{
    if ( this.Handle != IntPtr.Zero )
    {
        IntPtr hWndDeskTop = GetDesktopWindow();
        SetParent( this.Handle, hWndDeskTop );
    }

    base.OnHandleCreated(e);
}

当然,到目前为止看似已经完成了窗口出现在最上方。不过,如果有另外一个窗口被设置成Top-Most呢?你的窗口就不会在最上面了。所以,我们需要再一次设置topmost属性,这些通过SetWindowPos API实现,控件会在BeginDisplay中调用,以保证窗口永远是最上层的窗口。

绘图逻辑
绘图部分重写了两个函数OnPaint和OnPaintBackground。如果BackgroundImage属性设置了,那么OnPaintBackground会绘制它,否则以当前BackColor填充背景。

// splashscreen.cs

protected override void OnPaintBackground( PaintEventArgs e )
{
    if ( Bounds.Width > 0 && Bounds.Height > 0 && Visible )
    {
        try
        {
            Rectangle rect = 
                new Rectangle(0, 0, Bounds.Width, Bounds.Height);
            Graphics g = e.Graphics;
            g.SetClip(e.ClipRectangle);
            if (BackgroundImage == null)
            {
                SolidBrush solidBrush = new SolidBrush(BackColor);
                g.FillRectangle(solidBrush, rect);
                solidBrush.Dispose();
            }
            else
            {
                g.DrawImage(BackgroundImage, rect, 0, 0, 
                    BackgroundImage.Width, BackgroundImage.Height, 
                    GraphicsUnit.Pixel);
            }
        }
        catch (Exception exception)
        {
            System.Diagnostics.StackFrame stackFrame = 
                new System.Diagnostics.StackFrame(true);
            Console.WriteLine(
                "/nException: {0}, /n/t{1}, /n/t{2}, /n/t{3}/n",
                this.GetType().ToString(), stackFrame.GetMethod().ToString(),
                stackFrame.GetFileLineNumber(), exception.Message);
        }
    }
}

在OnPaint里,控件得到TitleString和CommentaryString值,计算大小和位置,使用Graphics.DrawString绘制文字。

// splashscreen.cs

protected override void OnPaint( PaintEventArgs e )
{
    if ( Bounds.Width > 0 && Bounds.Height > 0 && Visible )
    {
        try
        {
            Graphics g = e.Graphics;
            g.SetClip(e.ClipRectangle);
            g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            float nClientHeight = ClientRectangle.Height;
            //start the text two thirds down:
            m_nTextOffsetY = 
                Convert.ToInt32(Math.Ceiling(((nClientHeight / 3) * 2))) + 
                m_nLeading;

            if (TitleString != string.Empty)
            {
                Font fontTitle = new Font(Font, FontStyle.Bold);
                SizeF sizeF = g.MeasureString(TitleString, fontTitle, 
                    ClientRectangle.Width, m_stringFormat);
                m_nTextOffsetY += Convert.ToInt32(
                    Math.Ceiling(sizeF.Height));
                RectangleF rectangleF = new RectangleF(
                    ClientRectangle.Left + m_nTextOffsetX, 
                    ClientRectangle.Top + m_nTextOffsetY, sizeF.Width, 
                    sizeF.Height);
                SolidBrush brushFont = new SolidBrush(ForeColor);
                g.DrawString(TitleString, fontTitle, brushFont, 
                    rectangleF, m_stringFormat);
                brushFont.Dispose();
                fontTitle.Dispose();

                m_nTextOffsetY += m_nLeading;
            }

            if (CommentaryString != string.Empty)
            {
                SizeF sizeF = g.MeasureString(CommentaryString, Font, 
                    ClientRectangle.Width, m_stringFormat);
                m_nTextOffsetY += Convert.ToInt32(
                    Math.Ceiling(sizeF.Height));
                RectangleF rectangleF = 
                    new RectangleF(ClientRectangle.Left + m_nTextOffsetX, 
                    ClientRectangle.Top + m_nTextOffsetY, sizeF.Width, 
                    sizeF.Height);
                SolidBrush brushFont = new SolidBrush(ForeColor);
                g.DrawString(CommentaryString, Font, brushFont, 
                    rectangleF, m_stringFormat);
                brushFont.Dispose();
            }
        }
        catch (Exception exception)
        {
            System.Diagnostics.StackFrame stackFrame = 
                new System.Diagnostics.StackFrame(true);
            Console.WriteLine("/nException: {0}, /n/t{1}, /n/t{2}, 
                /n/t{3}/n", this.GetType().ToString(), 
                stackFrame.GetMethod().ToString(), 
                stackFrame.GetFileLineNumber(), exception.Message);
        }
    }
}