views:

188

answers:

2

Whilst playing around with resources in my visual studio 10 project, I came across a build action called "Splash Screen", which led me to find this article on the neat splash screen capabilities of WPF.

Hunting around, I found this MSDN article on making a splash screen in Windows Forms, but it's a different type of approach: rather than loading the splash screen using native code before the app loads, the WinForms version simply displays it whilst the main form is initialising.

Is there any way to achieve this superior type of splash screen in a WinForms app?

A: 

It is possible to use a non-WPF form for this, and it works quite happily. As an aside, it was a feature in VS2008 as well.

Rowland Shaw
+3  A: 

Yes. I did an implementation for our WPF application before it came packaged with .NET 3.5 SP1.

Basically, you create a Native Win32 window and display a BMP image whilst you are loading the assemblies and initialising your application. You can use other image formats, but BMP is preferred as it requires the least number of libraries loaded.

A quick google for "creating native splash window" came up with several articles. The most comprehensive that I found (from a quick scan) was by Bradley Grainger: Displaying a Splash Screen with C++. The article was written for WPF in mind, however the concept the same: create a native window, launch your application, close window.

I will have a look at the source for my implementation tomorrow at work and update my answer with an examples.

Until then, google and research the many examples already out there to get started.

UPDATE

As promised, below is a full example of implementing a splash screen (there is a bit to it).

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Windows.Interop;

namespace SplashScreen
{

    public class SplashScreenManager
    {
        static SplashScreen _current = null;

        static SplashScreenManager() {}
        SplashScreenManager() { }

        public static SplashScreen Create(Module module, int resourceID)
        {
            if (_current != null)
            {
                _current.Close();
                _current.Dispose();
            }

            _current = new SplashScreen(module, resourceID);
            return _current;
        }

        public static void Close()
        {
            if (_current == null)
                return;

            _current.Close();
            _current.Dispose();
            _current = null;
        }

        public static SplashScreen Current
        {
            get { return _current; }
        }

    }

    public class SplashScreen : IDisposable
    {                
        static bool IsClassRegistered = false;
        static string WindowClassName = "SplashScreenWindowClass";

        IntPtr _bitmapHandle = IntPtr.Zero;
        int _bitmapHeight;
        int _bitmapWidth;
        bool _isClosed;

        UnsafeNativeMethods.WndProc _splashWindowProcedureCallback;

        IntPtr _windowHandle = IntPtr.Zero;

        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        internal SplashScreen(Module module, int resourceID)
        {
            _bitmapHandle = UnsafeNativeMethods.LoadBitmap(Marshal.GetHINSTANCE(module), new IntPtr(resourceID));
            _splashWindowProcedureCallback = new UnsafeNativeMethods.WndProc(SplashWindowProcedure);
        }

        public void Close()
        {
            if (_isClosed)
                return;

            _isClosed = true;
            UnsafeNativeMethods.PostMessage(new HandleRef(this, _windowHandle), 0x10, IntPtr.Zero, IntPtr.Zero);
            if (_bitmapHandle != IntPtr.Zero)
            {
                UnsafeNativeMethods.DeleteObject(_bitmapHandle);
                _bitmapHandle = IntPtr.Zero;
            }
        }

        public void Close(IntPtr handle)
        {
            if (_windowHandle != IntPtr.Zero)
                UnsafeNativeMethods.SetForegroundWindow(handle);

            Close();
        }

        void CreateWindow()
        {
            if (!IsClassRegistered)
                RegisterClass();

            if (IsClassRegistered)
            {
                CreateWindowInternal();
                if (_windowHandle != IntPtr.Zero)
                    UnsafeNativeMethods.ShowWindow(_windowHandle, 5);

            }
        }

        public void Dispose()
        {
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            Close(IntPtr.Zero);
            GC.SuppressFinalize(this);
        }

        void GetBitmapDimensions()
        {
            int cb = Marshal.SizeOf(typeof(UnsafeNativeMethods.BITMAP));
            IntPtr lpvObject = Marshal.AllocCoTaskMem(cb);
            UnsafeNativeMethods.GetObject(_bitmapHandle, cb, lpvObject);
            UnsafeNativeMethods.BITMAP bitmap = (UnsafeNativeMethods.BITMAP)Marshal.PtrToStructure(lpvObject, typeof(UnsafeNativeMethods.BITMAP));
            _bitmapWidth = bitmap.bmWidth;
            _bitmapHeight = bitmap.bmHeight;
            Marshal.FreeCoTaskMem(lpvObject);
        }

        void OnPaint(IntPtr hdc)
        {
            if (_bitmapHandle != IntPtr.Zero)
            {
                IntPtr ptr = UnsafeNativeMethods.CreateCompatibleDC(hdc);
                IntPtr hgdiobj = UnsafeNativeMethods.SelectObject(ptr, _bitmapHandle);
                UnsafeNativeMethods.BitBlt(hdc, 0, 0, _bitmapWidth, _bitmapHeight, ptr, 0, 0, 0xcc0020);
                UnsafeNativeMethods.SelectObject(ptr, hgdiobj);
                UnsafeNativeMethods.DeleteDC(ptr);
            }
        }

        void CreateWindowInternal()
        {
            int systemMetrics = UnsafeNativeMethods.GetSystemMetrics(0);
            int num4 = UnsafeNativeMethods.GetSystemMetrics(1);
            uint dwStyle = 0x80000000;
            uint dwExStyle = 0x188;
            IntPtr moduleHandle = UnsafeNativeMethods.GetModuleHandle(null);
            IntPtr desktopWindow = UnsafeNativeMethods.GetDesktopWindow();
            _windowHandle = UnsafeNativeMethods.CreateWindowEx(dwExStyle, WindowClassName, "", dwStyle, (systemMetrics - _bitmapWidth) / 2, (num4 - _bitmapHeight) / 2, _bitmapWidth, _bitmapHeight, desktopWindow, IntPtr.Zero, moduleHandle, IntPtr.Zero);
        }

        void RegisterClass()
        {
            IntPtr moduleHandle = UnsafeNativeMethods.GetModuleHandle(null);
            UnsafeNativeMethods.WNDCLASSEX lpwcx = new UnsafeNativeMethods.WNDCLASSEX();
            lpwcx.cbSize = (uint)Marshal.SizeOf(typeof(UnsafeNativeMethods.WNDCLASSEX));
            lpwcx.cbClsExtra = 0;
            lpwcx.cbWndExtra = 0;
            lpwcx.hbrBackground = IntPtr.Zero;
            lpwcx.hCursor = IntPtr.Zero;
            lpwcx.hIcon = IntPtr.Zero;
            lpwcx.hIconSm = IntPtr.Zero;
            lpwcx.hInstance = moduleHandle;
            lpwcx.lpfnWndProc = _splashWindowProcedureCallback;
            lpwcx.lpszClassName = WindowClassName;
            lpwcx.lpszMenuName = null;
            lpwcx.style = 0;
            if (UnsafeNativeMethods.RegisterClassExW(ref lpwcx) != 0)
            {
                IsClassRegistered = true;
            }
        }

        public void Show()
        {
            if (_windowHandle == IntPtr.Zero)
            {
                Thread thread = new Thread(new ThreadStart(ThreadMethod));
                thread.IsBackground = true;
                thread.Start();
            }
        }

        [SuppressUnmanagedCodeSecurity]
        IntPtr SplashWindowProcedure(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
        {
            if (msg == 15)
            {
                UnsafeNativeMethods.PAINTSTRUCT lpPaint = new UnsafeNativeMethods.PAINTSTRUCT();
                IntPtr hdc = UnsafeNativeMethods.BeginPaint(hWnd, out lpPaint);
                OnPaint(hdc);
                UnsafeNativeMethods.EndPaint(hWnd, ref lpPaint);
                return IntPtr.Zero;
            }

            return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wParam, lParam);
        }

        [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
        void ThreadMethod()
        {
            if (_bitmapHandle != IntPtr.Zero)
            {
                GetBitmapDimensions();
                CreateWindow();
                MSG msg = new MSG();
                while (UnsafeNativeMethods.GetMessage(ref msg, _windowHandle, 0, 0) > 0)
                {
                    UnsafeNativeMethods.TranslateMessage(ref msg);
                    UnsafeNativeMethods.DispatchMessage(ref msg);
                }
                _windowHandle = IntPtr.Zero;
                GC.KeepAlive(this);
            }
        }

    }

    [SuppressUnmanagedCodeSecurity]
    internal sealed class UnsafeNativeMethods
    {
        // Fields
        internal const int GWL_EXSTYLE = -20;
        public const int LOGPIXELSX = 0x58;
        public const int LOGPIXELSY = 90;
        internal const uint MB_ICONASTERISK = 0x40;
        internal const uint MB_ICONERROR = 0x10;
        internal const uint MB_ICONEXCLAMATION = 0x30;
        internal const uint MB_ICONHAND = 0x10;
        internal const uint MB_ICONINFORMATION = 0x40;
        internal const uint MB_ICONQUESTION = 0x20;
        internal const uint MB_ICONWARNING = 0x30;
        internal const uint MB_OK = 0;
        internal const uint MB_OKCANCEL = 1;
        internal const uint MB_SETFOREGROUND = 0x10000;
        internal const uint MB_YESNO = 4;
        internal const uint MB_YESNOCANCEL = 3;
        internal const int SM_CXSCREEN = 0;
        internal const int SM_CYSCREEN = 1;
        public const int SPI_GETWORKAREA = 0x30;
        internal const uint SRCCOPY = 0xcc0020;
        public const int SW_HIDE = 0;
        internal const int SW_SHOW = 5;
        public const int SW_SHOWMAXIMIZED = 3;
        public const int SW_SHOWMINIMIZED = 2;
        public const int SW_SHOWNORMAL = 1;
        internal const int WM_CLOSE = 0x10;
        internal const uint WS_EX_TOOLWINDOW = 0x80;
        internal const uint WS_EX_TOPMOST = 8;
        internal const uint WS_EX_TRANSPARENT = 0x20;
        internal const uint WS_EX_WINDOWEDGE = 0x100;
        internal const uint WS_POPUP = 0x80000000;

        UnsafeNativeMethods() {}

        [DllImport("user32.dll")]
        public static extern IntPtr WindowFromPoint(POINT Point);
        [DllImport("user32.dll")]
        public static extern IntPtr ChildWindowFromPoint(IntPtr hWndParent, POINT Point);
        [DllImport("user32.dll")]
        public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
        [DllImport("user32.dll")]
        internal static extern IntPtr BeginPaint(IntPtr hwnd, out PAINTSTRUCT lpPaint);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("gdi32.dll")]
        internal static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, uint dwRop);
        [DllImport("gdi32.dll")]
        internal static extern IntPtr CreateCompatibleDC(IntPtr hdc);
        [DllImport("user32.dll", EntryPoint = "CreateWindowExW", CharSet = CharSet.Unicode)]
        internal static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
        [DllImport("user32.dll")]
        internal static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("gdi32.dll")]
        internal static extern bool DeleteDC(IntPtr hdc);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("gdi32.dll")]
        internal static extern bool DeleteObject(IntPtr hObject);
        [DllImport("user32.dll")]
        internal static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        internal static extern bool EndPaint(IntPtr hWnd, ref PAINTSTRUCT lpPaint);
        [DllImport("user32.dll")]
        public static extern IntPtr GetDC(IntPtr hWnd);
        [DllImport("user32.dll")]
        internal static extern IntPtr GetDC(HandleRef hWnd);
        [DllImport("user32.dll")]
        internal static extern IntPtr GetDesktopWindow();
        [DllImport("gdi32.dll")]
        public static extern int GetDeviceCaps(IntPtr hDC, int index);
        [DllImport("user32.dll", EntryPoint = "GetMessageW", CharSet = CharSet.Unicode, ExactSpelling = true)]
        internal static extern int GetMessage([In, Out] ref MSG msg, IntPtr hWnd, int uMsgFilterMin, int uMsgFilterMax);
        [DllImport("kernel32.dll", EntryPoint = "GetModuleHandleW", CharSet = CharSet.Unicode)]
        internal static extern IntPtr GetModuleHandle(string lpModuleName);
        [DllImport("gdi32.dll")]
        internal static extern int GetObject(IntPtr hgdiobj, int cbBuffer, IntPtr lpvObject);
        [DllImport("user32.dll")]
        internal static extern int GetSystemMetrics(int nIndex);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern int GetWindowLong(IntPtr handle, int index);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
        [DllImport("user32.dll", EntryPoint = "LoadBitmapW", CharSet = CharSet.Unicode)]
        internal static extern IntPtr LoadBitmap(IntPtr hInstance, IntPtr lpBitmapName);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        internal static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
        [return: MarshalAs(UnmanagedType.U2)]
        [DllImport("user32.dll")]
        internal static extern short RegisterClassExW([In] ref WNDCLASSEX lpwcx);
        [DllImport("user32.dll")]
        public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
        [DllImport("gdi32.dll")]
        internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        internal static extern bool SetForegroundWindow(IntPtr hWnd);
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern int SetWindowLong(IntPtr handle, int index, int dwNewLong);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        internal static extern bool TranslateMessage([In] ref MSG lpMsg);

        [StructLayout(LayoutKind.Sequential)]
        internal struct BITMAP
        {
            public int bmType;
            public int bmWidth;
            public int bmHeight;
            public int bmWidthBytes;
            public ushort bmPlanes;
            public ushort bmBitsPixel;
            public IntPtr bmBits;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PAINTSTRUCT
        {
            public IntPtr hdc;
            public bool fErase;
            public UnsafeNativeMethods.RECT rcPaint;
            public bool fRestore;
            public bool fIncUpdate;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
            public byte[] rgbReserved;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct WINDOWPLACEMENT
        {
            public int Length;
            public int Flags;
            public int ShowCmd;
            public UnsafeNativeMethods.POINT MinPosition;
            public UnsafeNativeMethods.POINT MaxPosition;
            public UnsafeNativeMethods.RECT NormalPosition;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct WNDCLASSEX
        {
            public uint cbSize;
            public uint style;
            public UnsafeNativeMethods.WndProc lpfnWndProc;
            public int cbClsExtra;
            public int cbWndExtra;
            public IntPtr hInstance;
            public IntPtr hIcon;
            public IntPtr hCursor;
            public IntPtr hbrBackground;
            public string lpszMenuName;
            public string lpszClassName;
            public IntPtr hIconSm;
        }

        internal delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
    }

}
Dennis Roche
This looks good. See also the reinvention by Olson: http://www.olsonsoft.com/blogs/stefanolson/post/A-better-WPF-splash-screen.aspx
Tom Wright
@Tom: I actually read Olsons post first and follow the links to find Bradley's article. I will update my answer later today when I get time.
Dennis Roche
@Tom: Updated with examples.
Dennis Roche