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)
{
// 如果未显示容器窗口,则枚举
// 顶层窗口,提取图标和文本,
// 并将其显示在容器窗口中
case IDH_NEXT:
{
// 在窗口层次结构中选择
// 下一个顶层窗口的图标
break;
}
case IDH_PREV:
{
// 在窗口层次结构中选择
// 上一个顶层窗口的图标
}
}
}
}
}
第二种实现键盘侦听的更高级的方法是同时使用 API SetWindowsHookEx(英文)和 WH_KEYBOARD_LL。该方法在当前桌面的全局范围内创建一个低级别的键盘挂钩层。在调用 SetWindowsHookEx 时指定的 LowLevelKeyboardProc 回调函数将接收所有的键盘输入。处理完键盘输入后,LowLevelKeyboardProc 应调用 CallNextHookEx 以使下一个挂钩链(很可能是目标应用程序)能够接收输入。由于 LowLevelKeyboardProc 接收了所有的键盘事件,因此可以很容易地将其用作一个状态机,用于侦听同时按下的 Alt 和 Tab 组合键。如果该应用程序实现它自己的 AltTab 机制,则此时将执行窗口枚举算法,并从 LowLevelKeyboardHook 中返回而不把最后的 AltTab 键事件转给其他应用程序。
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:
{
// 用户按下 Shift 键
fShiftPressed = TRUE;
break;
}
case VK_TAB:
{
if (pkbdllhook->flags & LLKHF_ALTDOWN)
{
// 用户按下 Alt+Tab,执行 AltTab 热键处理程序
fHandled = TRUE;
}
break;
}
case VK_ESCAPE:
{
if (pkbdllhook->flags & LLKHF_ALTDOWN)
{
// 用户按下 Esc 键,关闭 AltTab 容器窗口
// 并且不切换到选定窗口
fHandled = TRUE;
}
break;
}
}
break;
case WM_KEYUP:
case WM_SYSKEYUP:
switch (pkbdllhook->vkCode)
{
case VK_LMENU:
case VK_RMENU:
{
// 用户释放 Alt 键,关闭 AltTab 容器窗口
// 并切换到选定窗口
break;
}
case VK_LSHIFT:
case VK_RSHIFT:
{
// 用户释放 Shift 键
fShiftPressed = FALSE;
break;
}
}
break;
}
}
return (fHandled ? TRUE : CallNextHookEx(hhook, nCode, wParam, lParam));
}
枚举顶层应用程序窗口
使用 Win32 API EnumWindows(英文)可以直接枚举顶层应用程序窗口。这个 API 接受 EnumFunc 回调函数作为参数。对于桌面上的每个顶层窗口,系统将用顶层窗口的窗口句柄作为参数,来调用 EnumFunc 函数。并不是所有的顶层窗口都应显示在 AltTab 列表中。需要查询窗口的一些属性,并且必须满足几个条件:窗口是应用程序窗口吗?窗口能被激活吗?窗口可视吗?窗口是 ToolWindow 吗?
接收到 AltTab 事件之后,TaskSwitcher 便开始使用 EnumWindows 枚举桌面上的顶层窗口。系统为每个顶层窗口调用回调函数。满足条件的窗口将被添加到窗口列表中,并显示在 AltTab 列表中。
显示顶层应用程序窗口
在 AltTab 列表的 UI 显示中,TaskSwitcher 使用了很多 Windows XP 的新编程功能。它使用新的 API DrawShadowText 来显示选定的应用程序的文本,使用新的 API PrintWindow 来生成窗口预览。最后,而且也许是应用程序开发人员最感兴趣的,TaskSwitcher 使用了 Windows XP 的新外观风格。
收集窗口信息
生成要在 AltTab 列表中显示的窗口列表后,会检索列表中每个窗口的各种属性并将其显示在预览容器中。通过向该窗口发送一个 WM_GETICON(英文)窗口消息,在列表中显示每个窗口的图标。当用户按 Tab 键在列表中移动时,列表中选定的应用程序图标的图标和文本将显示在预览容器的顶部。通过使用 API GetWindowText(英文)来检索每个窗口的标题文本。有趣的是,它使用了新的 API comctl32 v6 API DrawShadowText(英文)来显示应用程序文本。该 API 是 Windows XP 的新增功能,采用了 API DrawText 的所有相同参数,还有两个表示文本颜色和阴影颜色的 COLORREF 参数,以及阴影的 x 偏移量和 y 偏移量。
绘制窗口预览
TaskSwitcher 还可以显示选定窗口的缩略图预览。(除非最小化所预览的窗口,因为此时将只显示该窗口的标题栏。)绘制缩略图预览时,TaskSwitcher 采用了一些高级的 Win32 绘图技术,例如双重缓冲和半色调缩放等。然而,获取窗口预览的核心技术是新的 Windows XP user32 API PrintWindow。PrintWindow 带有一个窗口句柄、一个 hdc 和一个保留标志。该 API 使用窗口重定向,将窗口的快照绘制到 hdc 中。
// 制作窗口 hwnd 的快照,该窗口存储在内存设备环境 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);
}
使用外观风格 API 来显示容器
所有这些都绘制在容器窗口上。容器窗口的背景体现了 Windows XP 的新外观风格。也就是说,它和 Windows XP 的其余部分具有相同的外观,包括圆角窗口以及与标题栏类似的更具质感的图案背景。显示容器背景时,TaskSwitcher 使用了 uxtheme.h 中的很多新的主题 API,例如 OpenThemeData(英文)、CloseThemeData(英文)、GetThemeBackgroundRegion(英文)和 DrawThemeBackground(英文)。在本例中,我们把用于开始面板顶部的外观风格用作容器窗口的背景。
#include <uxtheme.h>
#include <tmschema.h>
// AltTab 列表容器窗口的对话框程序
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)
{
// 获取要用于绘制容器窗口的
// 背景区域部分并将其应用于
// 对话框。
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)
{
// 外观风格处于活动状态,使用外观
// 风格 API 进行绘制。
RECT rc;
GetWindowRect(hwnd, &rc);
OffsetRect(&rc, -rc.left, -rc.top);
DrawThemeBackground(htheme, hdc, SPP_USERPANE, 0, &rc, NULL);
}
else
{
// 外观风格不处于活动状态,按传统
// 窗口样式进行绘制。
}
}
EndPaint(hwnd, &ps);
break;
}
case WM_THEMECHANGED:
{
// 外观风格已更改,关闭现有的 htheme 并尝试
// 打开一个新的 htheme。
if (htheme)
{
CloseThemeData(htheme);
}
htheme = OpenThemeData(hwnd, L"StartPanel");
break;
}
}
}
图 3:TaskSwitcher AltTab 容器窗口
使用 Comctl32.dll 版本 6
Taskswitcher 利用了 comctl32.dll 版本 6 中的一些新功能。例如,图标列表使用 ListView 控件制作;容器的背景使用了匹配的背景水印,从而达到与窗口其余部分的自然融合。此外,API DrawShadowText 也是在 comctl32 v6 中找到的。
Comctl32 版本 6 是一个并行 DLL,即 comctl32.dll 版本 5 和版本 6 是同时安装在系统上的。默认情况下,当应用程序与 comctl32.lib 静态链接时,将使用版本 5。为了使应用程序能够使用版本 6,必须提供一个如下的应用程序声明文件:
<?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>TaskSwitcher:AltTab 替代程序。</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="X86"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
然后通过在 .rc 文件中指定以下行,将该声明文件编入应用程序的资源部分。
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "TaskSwitch.exe.manifest"
总结
Windows XP 提供了一个全新的用户界面,包括新的外观风格以及能够直观捕获窗口内容的能力。使用本文介绍的技术,开发人员可以利用外观风格 API 为其应用程序设计一个可以与 Windows XP 其余部分的外观相匹配的独特外观。使用 PrintWindow,开发人员可以制作指定窗口的快照并将其插入设备环境。
……