已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
Windows 消息机制初步探索 之 快捷键与非独占对话框

Windows中,快捷键(accelerators)将WM_KEYDOWN按键消息转换为WM_COMMAND菜单命令消息,使得用按键来模拟菜单命令变得十分方便。快捷键一般在Windows程序的资源文件(.rc)中定义。使用快捷键需要对消息循环进行一些修改。

Windows中,对话框分为独占对话框(modal dialog)和非独占对话框(modeless dialog)。对话框的模板也可在资源文件(.rc)中定义,并且Visual C++对其提供了可视化的设计器。独占对话框的消息由系统托管,而非独占对话框则需要对原有的消息循环进行修改。

为了做好下一步的实验,我们要首先要为我们的程序定义一个快捷键和对话框:
捕获10.png

建立的快捷键和对话框如图所示:
捕获13.png

其中我们需要对快捷键进行一些修改,将命令ID改为0x111,将键改为VK_RETURN(回车):
捕获11.png

然后在程序中导入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(2730001 / 0111
(wParam高四位表示命令类型,0是菜单,1是快捷键,其它的表示控件通知;低四位表示命令编号)
捕获14.png

二、非独占对话框

非独占对话框使用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)显示。

日常生活中常见的对话框是『查找和替换』对话框,如图所示。和一般的独占式对话框不同,在打开非独占对话框时,主窗口仍然可以被访问,程序也会继续运行。
捕获15.png

在对话框中使用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转换为了一系列的焦点移动消息:
捕获17.png
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,可以移动焦点:
捕获18.png

删除掉IsDialogMessageW(hDialog, &msg)调用时,运行结果如下,可以看到对话框仅接收到了WM_KEYDOWN和WM_CHAR消息:
捕获16.png
添加ShowWindow(hDialog, SW_SHOW)后,在显示的对话框中按Tab键,不能移动焦点
捕获19.png
文号 / 784355

千古风流
名片发私信
学术分 4
总主题 466 帖总回复 2942 楼拥有证书:进士 学者 笔友
注册于 2009-05-30 21:22最后登录 2019-01-31 17:16
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步

个人简介

暂未填写
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
等待中...
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
处理中..
处理失败
插入表情
我的表情
共享表情
Emoji
上传
注意事项
最大尺寸100px,超过会被压缩。为保证效果,建议上传前自行处理。
建议上传自己DIY的表情,严禁上传侵权内容。
点击重试等待上传{{s.progress}}%处理中...已上传,正在处理中
空空如也~
处理中...
处理失败
加载中...
草稿箱
加载中...
此处只插入正文,如果要使用草稿中的其余内容,请点击继续创作。
{{fromNow(d.toc)}}
{{getDraftInfo(d)}}
标题:{{d.t}}
内容:{{d.c}}
继续创作
删除插入插入
插入公式
评论控制
加载中...
文号:{{pid}}
加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}
ID: {{user.uid}}