Displaying a Splash Screen with C++ (Part II)

This is Part II of a series on creating a splash screen application in native code. For more information, see the Introduction and the Code License.

Part II: Displaying the Window

In Part I I showed how to create a HBITMAP with the splash screen image. This installment will show how to create a window that displays that image.

Each Win32 window needs a window class, and the splash screen window is no different. The window class for the splash screen window will be fairly standard, although the interesting thing is that we can use DefWindowProc as the WndProc because we don’t actually need special processing for any window messages. The window class also specifies an icon because I prefer to have splash screens show up in the Alt+Tab list, and it looks better if we provide an icon. (Note that in this code, error handling has been omitted.)

// Window Class name
const TCHAR * c_szSplashClass = _T("SplashWindow");

// Registers a window class for the splash and splash owner windows.
void RegisterWindowClass()
{
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = DefWindowProc;
    wc.hInstance = g_hInstance;
    wc.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_SPLASHICON));
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = c_szSplashClass;
    RegisterClass(&wc);
}

When creating the windows, I use an old trick (a hidden owner window) to make the splash screen appear in the Alt+Tab list, but not in the taskbar. If you wanted the splash screen to also appear in the taskbar, you could drop the hidden owner window. If you didn’t want it to appear in the Alt+Tab list or the taskbar, you could use the WS_EX_TOOLWINDOW extended window style (and omit the owner window).

// Creates the splash owner window and the splash window.
HWND CreateSplashWindow()
{
    HWND hwndOwner = CreateWindow(c_szSplashClass, NULL, WS_POPUP,
        0, 0, 0, 0, NULL, NULL, g_hInstance, NULL);
    return CreateWindowEx(WS_EX_LAYERED, c_szSplashClass, NULL, WS_POPUP | WS_VISIBLE,
        0, 0, 0, 0, hwndOwner, NULL, g_hInstance, NULL);
}

The window now exists, but isn’t sized or positioned correctly, and has no content. The UpdateLayeredWindow function can be used to correct all these problems at once. There are several steps to this process:

  • Get the dimensions of the splash screen image
  • Get the dimensions of the work area on the primary monitor
  • Calculate the location of the splash screen (in order to centre it on the primary monitor)
  • Create a memory DC that contains the splash image
  • Specify a blend function that will use the per-pixel alpha of the splash image
  • Pass all this information to UpdateLayeredWindow
// Calls UpdateLayeredWindow to set a bitmap (with alpha) as the content of the splash window.
void SetSplashImage(HWND hwndSplash, HBITMAP hbmpSplash)
{
    // get the size of the bitmap
    BITMAP bm;
    GetObject(hbmpSplash, sizeof(bm), &bm);
    SIZE sizeSplash = { bm.bmWidth, bm.bmHeight };

    // get the primary monitor's info
    POINT ptZero = { 0 };
    HMONITOR hmonPrimary = MonitorFromPoint(ptZero, MONITOR_DEFAULTTOPRIMARY);
    MONITORINFO monitorinfo = { 0 };
    monitorinfo.cbSize = sizeof(monitorinfo);
    GetMonitorInfo(hmonPrimary, &monitorinfo);

    // center the splash screen in the middle of the primary work area
    const RECT & rcWork = monitorinfo.rcWork;
    POINT ptOrigin;
    ptOrigin.x = rcWork.left + (rcWork.right - rcWork.left - sizeSplash.cx) / 2;
    ptOrigin.y = rcWork.top + (rcWork.bottom - rcWork.top - sizeSplash.cy) / 2;

    // create a memory DC holding the splash bitmap
    HDC hdcScreen = GetDC(NULL);
    HDC hdcMem = CreateCompatibleDC(hdcScreen);
    HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpSplash);

    // use the source image's alpha channel for blending
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;

    // paint the window (in the right location) with the alpha-blended bitmap
    UpdateLayeredWindow(hwndSplash, hdcScreen, &ptOrigin, &sizeSplash,
        hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);

    // delete temporary objects
    SelectObject(hdcMem, hbmpOld);
    DeleteDC(hdcMem);
    ReleaseDC(NULL, hdcScreen);
}

The beauty of layered windows and the UpdateLayeredWindow function is that the splash window doesn’t have to respond to WM_PAINT messages; Windows will paint it (and blend it correctly with the windows below it) by default.

Now that the splash screen is being displayed, we need to launch the actual application (and then dismiss the splash screen); the next installments will cover this.

Posted by Bradley Grainger on September 25, 2008