Windows 消息机制初步探索 之 快捷键与非独占对话框
Windows中,快捷键(accelerators)将WM_KEYDOWN按键消息转换为WM_COMMAND菜单命令消息,使得用按键来模拟菜单命令变得十分方便。快捷键一般在Windows程序的资源文件(.rc)中定义。使用快捷键需要对消息循环进行一些修改。
Windows中,对话框分为独占对话框(modal dialog)和非独占对话框(modeless dialog)。对话框的模板也可在资源文件(.rc)中定义,并且Visual C++对其提供了可视化的设计器。独占对话框的消息由系统托管,而非独占对话框则需要对原有的消息循环进行修改。
为了做好下一步的实验,我们要首先要为我们的程序定义一个快捷键和对话框:
建立的快捷键和对话框如图所示:
其中我们需要对快捷键进行一些修改,将命令ID改为
0x111,将键改为
VK_RETURN(回车):
然后在程序中导入resource.h中的资源符号:
<code class="lang-cpp">// 导入资源符号
#include "resource.h"</code>
一、快捷键的消息机制
在程序中,加载快捷键可以用以下方式:
HACCEL 快捷键句柄 = LoadAcceleratorsW(模块句柄, (wchar_t*)快捷键序号);
其中模块句柄可以为0,表示当前模块。
使用快捷键的方法是在消息循环中使用TranslateAcceleratorW。TranslateAcceleratorW读取到将WM_KEYDOWN消息,将会转换为WM_COMMAND菜单命令并分发,返回TRUE表示已处理。否则返回FALSE表示未处理。
TranslateAcceleratorW的作用是
处理和分发快捷键消息,已处理的消息不需要再被分发。
TranslateAcceleratorW的用法如下:
BOOL 是否已处理 = TranslateAcceleratorW(窗口句柄, 快捷键句柄, &MSG结构);
加了快捷键以后的消息循环如下:
<code class="lang-cpp">// 加载快捷键
hAccel = LoadAcceleratorsW(0, (wchar_t*)IDR_ACCELERATOR1);
MSG msg;
// 读取线程消息队列,后边三个参数都是限定条件,填0就可以
while (GetMessageW(&msg, 0, 0, 0))
{
// 转换快捷键为WM_COMMAND(273)并分发,处理后的消息不需要再次分发!
if (!TranslateAcceleratorW(vWindow, hAccel, &msg))
{
// 将键盘消息转换为字符消息
TranslateMessage(&msg);
// 将获取到的消息转发给WndProc
DispatchMessageW(&msg);
}
}
// 返回WM_QUIT的退出码
return (int)msg.wParam;</code>
使用快捷键的示例如下:
<code class="lang-cpp">#include <stdio.h>
#include <windows.h>
#include <process.h>
// 导入资源符号
#include "resource.h"
HWND vWindow; // 一个不可见的窗口
DWORD mainthread; // 主线程的ID
HACCEL hAccel; // 快捷键
HWND hDialog; // 非独占对话框
// 新建的一个线程,模拟操作系统向程序发送消息
void thread1(void*)
{
Sleep(1000);
// 发送一个hWnd为0的消息
// 该消息只能由GetMessageW循环接收
PostThreadMessageW(mainthread, WM_PAINT, 4, 5);
Sleep(1000);
// 发送一个hWnd不为0的消息
// 该消息将由GetMessageW循环接收
// 接收后可通过DispatchMessageW分发给WndProc消息处理函数
PostMessageW(vWindow, WM_PAINT, 2, 3);
Sleep(1000);
// 直接调用WndProc消息处理函数
// 该消息将不会被GetMessageW接收!
SendMessageW(vWindow, WM_PAINT, 6, 7);
Sleep(1000);
// 发送键盘消息,如果调用了TranslateMessage,该窗口还将接收一个WM_CHAR(258)消息
PostMessageW(vWindow, WM_KEYDOWN, 'A', 0);
Sleep(1000);
// 发送已定义快捷键的VK_RETURN键盘消息,将转换为WM_COMMAND(273),wParam=0x11111
PostMessageW(vWindow, WM_KEYDOWN, VK_RETURN, 0);
}
LONG_PTR __stdcall WndProc(HWND hwnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
printf("hWnd=%p message=%u wParam=%p lParam=%p [WndProc]\n",
hwnd, msg,wp, lp);
return 0;
}
int main()
{
// 获得主线程的ID
mainthread = GetCurrentThreadId();
// 简单地创建一个不可见窗口,该窗口与主进程绑定
vWindow = CreateWindowExW(0, L"static", 0, 0,
0, 0, 0, 0, 0, 0, 0, 0);
// 替换该窗口的WndProc函数
SetWindowLongPtrW(vWindow, GWLP_WNDPROC, (LONG_PTR)WndProc);
// 加载快捷键
hAccel = LoadAcceleratorsW(0, (wchar_t*)IDR_ACCELERATOR1);
// 创建另一个线程用以发送消息
_beginthread(thread1, 0, 0);
MSG msg;
// 读取线程消息队列,后边三个参数都是限定条件,填0就可以
while (GetMessageW(&msg, 0, 0, 0))
{
// 打印消息的详细信息
printf("hWnd=%p message=%u wParam=%p lParam=%p, time=%u pt=(%d,%d)\n",
msg.hwnd, msg.message, msg.wParam, msg.lParam,
msg.time, msg.pt.x, msg.pt.y);
// 转换快捷键为WM_COMMAND(273)并分发,处理后的消息不需要再次分发!
if (!TranslateAcceleratorW(vWindow, hAccel, &msg))
{
// 将键盘消息转换为字符消息
TranslateMessage(&msg);
// 将获取到的消息转发给WndProc
DispatchMessageW(&msg);
}
}
// 返回WM_QUIT的退出码
return (int)msg.wParam;
}</process.h></windows.h></stdio.h></code>
运行效果如下,注意发送的WM_KEYDOWN(
256)VK_RETURN(
0xD)被转换为WM_COMMAND(
273)
0001 / 0111:
(wParam高四位表示命令类型,0是菜单,1是快捷键,其它的表示控件通知;低四位表示命令编号)
二、非独占对话框
非独占对话框使用CreateDialogParamW函数创建,它返回一个窗口句柄:
HWND 对话框句柄 = CreateDialogParamW(模块句柄, (wchar_t*)对话框模板序号, 父窗口句柄, 对话框处理函数, 可选参数);
对话框处理函数DlgProc的形式如下,DlgProc对于已处理的消息返回TRUE,未处理的返回FALSE:
INT_PTR __stdcall DlgProc(HWND hwnd, unsigned int msg, UINT_PTR wparam, LONG_PTR lparam);
CreateDialogParamW不会立即显示对话框,需要使用
ShowWindow(对话框句柄, SW_SHOW)显示。
日常生活中常见的对话框是『查找和替换』对话框,如图所示。和一般的独占式对话框不同,在打开非独占对话框时,主窗口仍然可以被访问,程序也会继续运行。
在对话框中使用Tab等按键可以移动控件焦点,这是对话框的标准功能。但是传统TranslateMessage只能将键盘消息WM_KEYDOWN转换成字符消息WM_CHAR。为了将Tab等按键翻译成焦点移动动作,需使用
IsDialogMessageW。
IsDialogMessageW的作用是
处理和分发对话框消息,已处理的消息不需要再被分发。
实际上,不只是对话框能用,其它的窗口如果想实现使用Tab等按键移动控件焦点,也可以使用这个函数来实现。
IsDialogMessageW的用法如下:
BOOL 是否已处理 = IsDialogMessageW(对话框句柄, &MSG结构);
加了IsDialogMessageW的消息循环如下:
<code class="lang-cpp">// 加载快捷键
hAccel = LoadAcceleratorsW(0, (wchar_t*)IDR_ACCELERATOR1);
// 加载非独占对话框
hDialog = CreateDialogParamW(0, (wchar_t*)IDD_DIALOG1, vWindow, DlgProc, 0);
ShowWindow(hDialog, SW_SHOW); // 显示对话框
MSG msg;
// 读取线程消息队列,后边三个参数都是限定条件,填0就可以
while (GetMessageW(&msg, 0, 0, 0))
{
// 如果对话框已加载,处理和分发对话框消息,处理后的消息不需要被再次分发!
if (!hDialog || !IsDialogMessageW(hDialog, &msg))
{
// 转换快捷键为WM_COMMAND(273)并分发,处理过的消息不需要再次分发!
if (!TranslateAcceleratorW(vWindow, hAccel, &msg))
{
// 将键盘消息转换为字符消息
TranslateMessage(&msg);
// 将获取到的消息转发给WndProc
DispatchMessageW(&msg);
}
}
}
// 返回WM_QUIT的退出码
return (int)msg.wParam;</code>
非独占对话框示例程序:
<code class="lang-cpp">#include <stdio.h>
#include <windows.h>
#include <process.h>
// 导入资源符号
#include "resource.h"
HWND vWindow; // 一个不可见的窗口
DWORD mainthread; // 主线程的ID
HACCEL hAccel; // 快捷键
HWND hDialog; // 非独占对话框
// 新建的一个线程,模拟操作系统向程序发送消息
void thread1(void*)
{
Sleep(1000);
// 发送一个hWnd为0的消息
// 该消息只能由GetMessageW循环接收
PostThreadMessageW(mainthread, WM_PAINT, 4, 5);
Sleep(1000);
// 发送一个hWnd不为0的消息
// 该消息将由GetMessageW循环接收
// 接收后可通过DispatchMessageW分发给WndProc消息处理函数
PostMessageW(vWindow, WM_PAINT, 2, 3);
Sleep(1000);
// 直接调用WndProc消息处理函数
// 该消息将不会被GetMessageW接收!
SendMessageW(vWindow, WM_PAINT, 6, 7);
Sleep(1000);
// 发送键盘消息,如果调用了TranslateMessage,该窗口还将接收一个WM_CHAR(258)消息
PostMessageW(vWindow, WM_KEYDOWN, 'A', 0);
Sleep(1000);
// 发送已定义快捷键的VK_RETURN键盘消息,将转换为WM_COMMAND(273),wParam=0x11111
PostMessageW(vWindow, WM_KEYDOWN, VK_RETURN, 0);
Sleep(1000);
// 发送Tab键到非独占对话框
// 若在消息循环中调用了IsDialogMessageW,则会转换为一系列焦点移动消息
// 否则只会转换为普通的字符消息
PostMessageW(hDialog, WM_KEYDOWN, VK_TAB, 0);
}
LONG_PTR __stdcall WndProc(HWND hwnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
printf("hWnd=%p message=%u wParam=%p lParam=%p [WndProc]\n",
hwnd, msg,wp, lp);
return 0;
}
INT_PTR __stdcall DlgProc(HWND hwnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
printf("hWnd=%p message=%u wParam=%p lParam=%p [DlgProc]\n",
hwnd, msg, wp, lp);
return FALSE;
}
int main()
{
// 获得主线程的ID
mainthread = GetCurrentThreadId();
// 简单地创建一个不可见窗口,该窗口与主进程绑定
vWindow = CreateWindowExW(0, L"static", 0, 0,
0, 0, 0, 0, 0, 0, 0, 0);
// 替换该窗口的WndProc函数
SetWindowLongPtrW(vWindow, GWLP_WNDPROC, (LONG_PTR)WndProc);
// 加载快捷键
hAccel = LoadAcceleratorsW(0, (wchar_t*)IDR_ACCELERATOR1);
// 加载非独占对话框
hDialog = CreateDialogParamW(0, (wchar_t*)IDD_DIALOG1, vWindow, DlgProc, 0);
//ShowWindow(hDialog, SW_SHOW); // 不显示对话框,防止多余的消息产生
// 创建另一个线程用以发送消息
_beginthread(thread1, 0, 0);
MSG msg;
// 读取线程消息队列,后边三个参数都是限定条件,填0就可以
while (GetMessageW(&msg, 0, 0, 0))
{
// 打印消息的详细信息
printf("hWnd=%p message=%u wParam=%p lParam=%p, time=%u pt=(%d,%d)\n",
msg.hwnd, msg.message, msg.wParam, msg.lParam,
msg.time, msg.pt.x, msg.pt.y);
// 如果对话框已加载,处理和分发对话框消息,处理后的消息不需要被再次分发!
// 删掉这一句,则对话框不能使用Tab键移动焦点
if (!hDialog || !IsDialogMessageW(hDialog, &msg))
{
// 转换快捷键为WM_COMMAND(273)并分发,处理过的消息不需要再次分发!
if (!TranslateAcceleratorW(vWindow, hAccel, &msg))
{
// 将键盘消息转换为字符消息
TranslateMessage(&msg);
// 将获取到的消息转发给WndProc
DispatchMessageW(&msg);
}
}
}
// 返回WM_QUIT的退出码
return (int)msg.wParam;
}</process.h></windows.h></stdio.h></code>
程序运行结果如下,可以看到Tab键消息被IsDialogMessageW转换为了一系列的焦点移动消息:
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,可以移动焦点:
当
删除掉IsDialogMessageW(hDialog, &msg)调用时,运行结果如下,可以看到对话框仅接收到了WM_KEYDOWN和WM_CHAR消息:
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,
不能移动焦点: