Windows 消息机制初步探索 之 快捷键与非独占对话框
Windows中,快捷键(accelerators)将WM_KEYDOWN按键消息转换为WM_COMMAND菜单命令消息,使得用按键来模拟菜单命令变得十分方便。快捷键一般在Windows程序的资源文件(.rc)中定义。使用快捷键需要对消息循环进行一些修改。
Windows中,对话框分为独占对话框(modal dialog)和非独占对话框(modeless dialog)。对话框的模板也可在资源文件(.rc)中定义,并且Visual C++对其提供了可视化的设计器。独占对话框的消息由系统托管,而非独占对话框则需要对原有的消息循环进行修改。
为了做好下一步的实验,我们要首先要为我们的程序定义一个快捷键和对话框:
建立的快捷键和对话框如图所示:
其中我们需要对快捷键进行一些修改,将命令ID改为0x111,将键改为VK_RETURN(回车):
然后在程序中导入resource.h中的资源符号:
一、快捷键的消息机制
在程序中,加载快捷键可以用以下方式:
HACCEL 快捷键句柄 = LoadAcceleratorsW(模块句柄, (wchar_t*)快捷键序号);
其中模块句柄可以为0,表示当前模块。
使用快捷键的方法是在消息循环中使用TranslateAcceleratorW。TranslateAcceleratorW读取到将WM_KEYDOWN消息,将会转换为WM_COMMAND菜单命令并分发,返回TRUE表示已处理。否则返回FALSE表示未处理。
TranslateAcceleratorW的作用是处理和分发快捷键消息,已处理的消息不需要再被分发。
TranslateAcceleratorW的用法如下:
BOOL 是否已处理 = TranslateAcceleratorW(窗口句柄, 快捷键句柄, &MSG结构);
加了快捷键以后的消息循环如下:
使用快捷键的示例如下:
运行效果如下,注意发送的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的消息循环如下:
非独占对话框示例程序:
程序运行结果如下,可以看到Tab键消息被IsDialogMessageW转换为了一系列的焦点移动消息:
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,可以移动焦点:
当删除掉IsDialogMessageW(hDialog, &msg)调用时,运行结果如下,可以看到对话框仅接收到了WM_KEYDOWN和WM_CHAR消息:
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,不能移动焦点:
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键,不能移动焦点:
200字以内,仅用于支线交流,主线讨论请采用回复功能。