Using Windows XP Visual Styles and PrintWindow in Windows Applications

 

Paul Hellyar
Microsoft Corporation

October 25, 2001

Summary: This article develops an augmented AltTab application, TaskSwitcher, as a framework for demonstrating how the new visual styles of Windows XP, and PrintWindow, can be used in Windows applications. (13 printed pages)

Contents

Introduction
TaskSwitcher Application
Intercepting Keyboard Input
Enumerating Top-level Application Windows
Presenting the Top-level Application Windows
Using Comctl32.dll Version 6
Conclusion

Introduction

Microsoft® Windows® XP introduces a new visual style that provides an easy-to-use and richer user interface—for instance, windows with rounded corners, the more tactile appearance of the taskbar, and the UI elements that hot track when the mouse hovers over them.

ms997649.xpvisualstyles_fig1(en-us,MSDN.10).gif

Figure 1. Calculator and the Display Properties dialog box rendered in the new visual style

Windows XP also introduces the new printing API, PrintWindow. This API enables the caller to snapshot a visual copy of a window into a device context.

For an introductory article on visual styles and applying them to an application, see the technical article Using Windows XP Visual Styles in the MSDN Library. Whereas that article offers general, introductory information, the main purpose of this article is to provide a practical example of using the visual style APIs and the PrintWindow API. It will also provide a refresher on using some of the previously existing Win32 APIs.

Specifically, I will develop a TaskSwitcher application, which provides the same functionality as the existing AltTab mechanism in Windows today. In addition to displaying an icon list, however, the application will also show a thumbnail preview of the application that will be switched to. The container window, in which the application icons and preview are displayed, will be rendered using the visual style APIs—allowing the application to match the look and feel of whatever visual style the end user currently has selected.

TaskSwitcher Application

TaskSwitcher is designed to replace the existing AltTab application-switching mechanism of Windows XP. AltTab is the built-in Windows power user functionality allowing end users to quickly switch between their top-level application windows. When the hotkey combination Alt+Tab is pressed, Windows generates the list of open windows in which the end user is working. This list of open windows is presented as a group of icons, with one outlined by a selection rectangle. As the end user continues to hold the Alt key and presses the Tab key, the selection rectangle moves to the next icon. The selected icon represents the application that Windows will bring to the foreground once the Alt key is released.

ms997649.xpvisualstyles_fig12(en-us,MSDN.10).gif

Figure 2. Windows XP AltTab container window

Logically, this functionality can be broken down into three components. First, an application must listen for the key combination Alt+Tab. Upon receiving that key combination, it enumerates top-level application windows on the desktop. Lastly, it presents those windows in some sort of UI container, allowing the user to select the icon of the application they would like to switch to.

Intercepting Keyboard Input

Using the Win32 API, you can create an application that listens for particular keystrokes by utilizing any one of several methods. The simplest method involves using the API RegisterHotKey. This API takes an hwnd, an ID, a virtual key, and a key modifier. If this call is successful, the hwnd's WndProc will receive a WM_HOTKEY message with the wParam of the message equal to ID whenever the virtual key and key modifier is pressed. This occurs whether the listener application window is active or not. The following call would cause hwndApp to be notified with a WM_HOTKEY message whenever AltTab is pressed:

RegisterHotKey(hwndApp, IDH_ALTTAB, MOD_ALT, VK_TAB)

Before Windows XP, attempting to register AltTab as a hot key would fail. As of Windows XP, not only can you successfully register AltTab as a hotkey, Windows XP will also allow you to handle this event yourself, rather than initiate its own built-in AltTab hotkey handler.

// Create a dummy window that listens for the hotkey
HWND hwndApp = CreateWindow(WC_APP, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 
               NULL, THIS_EXE, NULL);
if (hwnd)
{
    // register Alt+Tab 
    RegisterHotKey(hwndApp, IDH_NEXT, MOD_ALT, VK_TAB);
    RegisterHotKey(hwndApp, IDH_PREV, MOD_ALT|MOD_SHIFT, VK_TAB);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

LRESULT WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_HOTKEY:
        {
            switch (wParam)
            {
                // if the container window is not showing, enumerate
                // the top-level windows, extract their icon and text,
                // and present them in a container window
                case IDH_NEXT:
                {
                    // select the icon of the next top-level window
                    // in the window hierarchy
                    break;
                }
                case IDH_PREV:
                {
                    // select the icon of the previous top-level
                    // window in the window hierarchy
                }
            }
        }
    }
}

A second more advanced method of implementing a keyboard listener is to use the API SetWindowsHookEx with WH_KEYBOARD_LL. This method creates a low-level keyboard hook layer globally across the current desktop. The LowLevelKeyboardProc callback function specified in the call to SetWindowsHookEx, receives all keyboard input. The LowLevelKeyboardProc should call CallNextHookEx after processing keyboard input, in order to allow the next hook chain (most likely the destination application) to receive the input. Since the LowLevelKeyboardProc receives all keyboard events, it can easily be implemented as a state machine that listens for the combination of Alt and Tab pressed simultaneously. If the application implements its own AltTab mechanism, it would execute the window enumeration algorithm at this point, and return from the LowLevelKeyboardHook without forwarding the last AltTab key event.

hhook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hinst, 0);

LRESULT LowLevelKeyboardProc(INT nCode, WPARAM wParam, LPARAM lParam)
{
    static BOOL fShiftPressed = FALSE;

    BOOL fHandled = FALSE;

    if (nCode == HC_ACTION)
    {
        KBDLLHOOKSTRUCT *pkbdllhook = (KBDLLHOOKSTRUCT *)lParam;

        switch (wParam)
        {
            case WM_SYSKEYDOWN:
                switch (pkbdllhook->vkCode)
                {
                    case VK_LSHIFT:
                    case VK_RSHIFT:
                    {
                        // the user pressed the shift key
                        fShiftPressed = TRUE;
                        break;
                    }
                    case VK_TAB:
                    {
                        if (pkbdllhook->flags & LLKHF_ALTDOWN)
                        {
                            // the user pressed Alt+Tab, execute AltTab hotkey handler
                            fHandled = TRUE;
                        }
                        break;
                    }
                    case VK_ESCAPE:
                    {
                        if (pkbdllhook->flags & LLKHF_ALTDOWN)
                        {
                            // the user pressed Escape, end the AltTab container 
                            // window without switching the selected window
                            fHandled = TRUE;
                        }
                        break;
                    }
                }

                break;

            case WM_KEYUP:
            case WM_SYSKEYUP:
                switch (pkbdllhook->vkCode)
                {
                    case VK_LMENU:
                    case VK_RMENU:
                    {
                        // the user let go of the Alt key, end the AltTab container
                        // window switching to the selected window
                        break;
                    }
                    case VK_LSHIFT:
                    case VK_RSHIFT:
                    {
                        // the user released the shift key
                        fShiftPressed = FALSE;
                        break;
                    }
                }

                break;
        }
    }

    return (fHandled ? TRUE : CallNextHookEx(hhook, nCode, wParam, lParam));
}

Enumerating Top-level Application Windows

Enumerating top-level application windows is straight forward using the Win32 API EnumWindows. This is a well-documented API that takes an EnumFunc callback function as a parameter. For each top-level window on the desktop, the EnumFunc will be called back by the system with the window handle of the top-level window as a parameter. Not all top-level windows should appear in the AltTab list. A number of properties of the window are queried, and various conditions must be satisfied: Is the window an application window? Can the window be activated? Is it visible? Is it a ToolWindow?

Upon receiving the AltTab event, TaskSwitcher begins enumerating top-level windows on the desktop using EnumWindows. The system calls back the callback function for each top-level window. Those windows that qualify are added to the list of windows that will be displayed in the AltTab list.

Presenting the Top-level Application Windows

In the UI presentation of the AltTab list, the TaskSwitcher uses a number of the new programmatic features of Windows XP. The selected application's text is rendered using the new API DrawShadowText. Window previews are generated using the new API PrintWindow. Lastly, and perhaps most interesting to application developers, TaskSwitcher uses the new visual style of Windows XP.

Gathering Window Information

After generating the list of windows to be displayed in the AltTab list, various attributes of each window in the list are retrieved and rendered in a preview container. The icon of each window is represented in a list by sending the window a WM_GETICON window message. As the user presses the Tab key to move through the list, the icon and text of the selected application icon in the list are displayed across the top of the preview container. I retrieved each window's caption text by using the API GetWindowText. An interesting point regarding the rendering of the application text is that it uses the new API comctl32 v6 API DrawShadowText. This API, new with Windows XP, takes all the same parameters as the API DrawText, in addition to two COLORREFs indicating the color of the text and the color of the shadow, and an x and y offset for the shadow.

Painting the Window Preview

TaskSwitcher also shows a thumbnail preview of the selected window. (Unless the window you're previewing is minimized, in which case only that window's title bar will be shown.) TaskSwitcher employs some advanced Win32 painting techniques, such as double buffering and halftone scaling, when painting the thumbnail preview. At the heart of obtaining the window preview, however, is the new Windows XP user32 API PrintWindow. PrintWindow takes a window handle, an hdc, and a reserved flag. The API uses window redirection to paint a snapshot of the window into the hdc.

// Takes a snapshot of the window hwnd, stored in the memory device context hdcMem
HDC hdc = GetWindowDC(hwnd);
if (hdc)
{
    HDC hdcMem = CreateCompatibleDC(hdc);
    if (hdcMem)
    {
        RECT rc;
        GetWindowRect(hwnd, &rc);

        HBITMAP hbitmap = CreateCompatibleBitmap(hdc, RECTWIDTH(rc), RECTHEIGHT(rc));
        if (hbitmap)
        {
            SelectObject(hdcMem, hbitmap);

            PrintWindow(hwnd, hdcMem, 0);

            DeleteObject(hbitmap);
        }
        DeleteObject(hdcMem);
    }
    ReleaseDC(hwnd, hdc);
}

Using Visual Style APIs to Render the Container

All this is painted on a container window. The container window background is aware of the new visual styles of Windows XP. That is, it has the same look and feel of the rest of Windows XP, including rounded window corners and a tactile pattern background similar to that of the title bars. When rendering the container background, TaskSwitcher uses many of the new theme APIs in uxtheme.h, such as OpenThemeData, CloseThemeData, GetThemeBackgroundRegion, and DrawThemeBackground. In this example, we apply the visual style used at the top of the start panel as the background for the container window.

#include <uxtheme.h>
#include <tmschema.h>

// Dialog proc for the AltTab list container window 
INT_PTR CALLBACK DlgProc(HWND hwnd, UINT uMsg, WPARAM, LPARAM lParam)
{
    static HTHEME htheme = NULL;

    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            htheme = OpenThemeData(hwnd, L"StartPanel");

            if (htheme)
            {
                // get the background region for the part we are going to
                // paint the container window with and apply it to the
                // dialog.

                HRGN hrgn = NULL;
                GetWindowRect(hwnd, &rc);
                OffsetRect(&rc, -rc.left, -rc.top);

                if (SUCCEEDED(GetThemeBackgroundRegion(htheme, NULL, 
                           SPP_USERPANE, 0, &rc, &hrgn)))
                {
                    SetWindowRgn(hwnd, hrgn, FALSE);
                }
            }

            break;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            if (hdc)
            {
                if (htheme)
                {
                    // Visual styles are active, paint using the visual style
                    // APIs

                    RECT rc;
                    GetWindowRect(hwnd, &rc);
                    OffsetRect(&rc, -rc.left, -rc.top);

                    DrawThemeBackground(htheme, hdc, SPP_USERPANE, 0, &rc, NULL);
                }
                else
                {
                    // Visual styles are not active, paint in the classic windows
                    // style.
                }
            }
            EndPaint(hwnd, &ps);

            break;
        }
        case WM_THEMECHANGED:
        {
            // the visual style has changed, close your existing htheme and try to
            // open a new one.
            if (htheme)
            {
                CloseThemeData(htheme);
            }
            htheme = OpenThemeData(hwnd, L"StartPanel");

            break;
        }
    }
}

ms997649.xpvisualstyles_fig3(en-us,MSDN.10).gif

Figure 3. TaskSwitcher AltTab container window

Using Comctl32.dll Version 6

Taskswitcher utilizes some of the of the new functionality features found in comctl32.dll version 6. For instance, the icon list is implemented using a ListView control with a background watermark that matches the background of the container, allowing it to blend in seamlessly with the rest of the window. In addition, the API DrawShadowText is found in comctl32 v6.

Comctl32 version 6 is a side-by-side DLL, which means both comctl32.dll versions 5 and 6 are installed on the system at one time. By default, when an application statically links with comctl32.lib, the application will use version 5. In order for an application to utilize version 6, it must provide an application manifest file similar to the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 
<assemblyIdentity 
version="1.0.0.0" 
processorArchitecture="X86" 
name="Microsoft.Shell.TaskSwitch " 
type="win32" 
/> 
<description>TaskSwitch an AltTab alternative.</description> 
<dependency> 
<dependentAssembly> 
<assemblyIdentity 
type="win32" 
name="Microsoft.Windows.Common-Controls" 
version="6.0.0.0" 
processorArchitecture="X86" 
publicKeyToken="6595b64144ccf1df" 
language="*" 
/> 
</dependentAssembly> 
</dependency> 
</assembly> 

The manifest file is then compiled into the resource section of the application by specifying the following line in the .rc file.

CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "TaskSwitch.exe.manifest" 

Conclusion

Windows XP delivers a radically redesigned user interface, including a new visual style, and the ability to visually capture windows' contents. Using the techniques demonstrated in this article, developers can utilize the visual style APIs to give their application a unique look that yet matches the look and feel of the rest of Windows XP. Using PrintWindow, developers can snapshot a visual copy of a given window into a device context.