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

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

Part V: Windows 7 Taskbar Compatibility

The new taskbar in Windows 7 introduces a significant usability problem with the program presented so far: the taskbar considers the splash screen app and the “real” app to be two separate programs, and gives them separate icons on the taskbar. If the user pins the shortcut (to the splash screen) that was added to the Start Menu, a new icon is added when the real application launches, which breaks the standard behaviour for pinned programs.

Windows 7 adds a number of new APIs dealing with Application User Model IDs, which “are used extensively by the taskbar in Windows 7 and later systems to associate processes, files, and windows with a particular application”. The SetCurrentProcessExplicitAppUserModelID function, specifically, lets us identify both processes (the splash screen and the application) as being part of the same logical application, so they get grouped under the same taskbar button. The guidelines on “How to Form an Application-Defined AppUserModelID” suggest that the company name, product name, and version be used to create an AppModelUserID; I’ll use “YourCompany.YourApp.1” in the following code.

The only complicating factor is that this API is only present in Windows 7, so it has to be called conditionally for backwards compatibility with Windows XP and Vista.

To the C++ splash screen application, add a call to this new function:

typedef HRESULT (__stdcall *SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC)(PCWSTR AppID);

// Gives this process an explicit App User Model ID, so that it can be treated as one item (with
//   the main application window and the shortcut) by Windows 7.
void SetAppUserModelId()
{
    // try to load Shell32.dll
    HMODULE hmodShell32 = LoadLibrary(L"shell32.dll");
    if (hmodShell32 != NULL)
    {
        // see if the function is exposed by the current OS
        SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC pfnSetCurrentProcessExplicitAppUserModelID =
            reinterpret_cast<SETCURRENTPROCESSEXPLICITAPPUSERMODELIDPROC>(GetProcAddress(hmodShell32,
                "SetCurrentProcessExplicitAppUserModelID"));
        if (pfnSetCurrentProcessExplicitAppUserModelID != NULL)
        {
            pfnSetCurrentProcessExplicitAppUserModelID(L"YourCompany.YourApp.1");
        }

        FreeLibrary(hmodShell32);
    }
}

To the C# WPF application, add similar code (with a version check):

/// <summary>

/// Sets the Windows 7 application user model ID that will apply to the entire process. This identifier allows an application to

/// group its associated processes and windows under a single taskbar button.

/// </summary>

/// <param name="appId">The application user model ID.</param>

public static void SetApplicationUserModelId(string appId)
{
    // check for Windows 7

    Version version = Environment.OSVersion.Version;
    if ((version.Major > 6) || (version.Major == 6 && version.Minor >= 1))
    {
        SafeNativeMethods.SetCurrentProcessExplicitAppUserModelID(appId);
    }
}

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("shell32.dll")]
    public static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
}

These methods should be called as soon as possible, and certainly before any windows are created.

Finally, your MSI that installs a desktop or Start menu shortcut for the application needs to set the System.AppUserModel.ID property on the installed shortcut, as detailed in the Windows 7 Taskbar support with the MsiShortcutProperty table blog post.

Posted by Bradley Grainger on December 16, 2009