已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
(三) 窗口的创建与消息处理

原来那个程序是不是感觉有点简单?这里我们要制作一个复杂一点的Windows程序 - 显示一个空白窗口。

出于稳定性考量,Visual C++编译器中int、long等基本类型基本上没有变化(16->32仅int变化了一次,64位没有变化),但是指针则16位、32位、64位一直在变化,为了更好地适应指针的变化,以及方便地与指针相互转换,Windows中定义了平台相关整数,它们随指针长度而变化:
INT_PTRUINT_PTR - 16/32/64位平台相关整数,前者有符号,后者无符号
LONG_PTRULONG_PTR - 32/32/64位平台相关整数,前者有符号,后者无符号
这四个类型在以后的编程中要经常用到,务必明白它们的意思。

关于调用约定:调用约定表示调用函数时参数的传递方式。在Windows中绝大多数WinAPI都是__stdcall,只有wsprintf等参数数量可变的函数是__cdecl,自己定义的各种Win32函数也最好采用__stdcall。关于调用约定的内部信息请参阅相关资料,这里不详述。

一、准备一个窗口消息处理函数

Windows中窗口事件的处理是靠窗口消息来完成的,为了处理窗口消息,每一个窗口都至少对应一个窗口消息处理函数,窗口消息处理函数的定义如下:
<code class="lang-cpp">LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    return DefWindowProc(hWnd, msg, wp, lp);
}</code>

程序在处理完消息之后,return 0;直接返回,否则应该调用DefWindowProc来处理不想处理的消息。
常用的消息如下:
WM_CREATE - 窗口创建时被调用
WM_DESTROY - 窗口销毁时被调用
WM_PAINT - 窗口绘制时被调用
WM_COMMAND - 控件或窗口有消息时被调用
WM_SIZE - 窗口大小改变时被调用
为了在同一个函数中处理不同消息,我们一般使用switch (msg) { case WM_XXX: ... }分支来编写WndProc函数:
<code class="lang-cpp">// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_CREATE: // 窗口创建
        // ...
        return 0;
    case WM_DESTROY: // 窗口销毁
        // ...
        return 0;
    // case ...
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}</code>

在这里,我们只需处理WM_DESTROY即可:
<code class="lang-cpp">// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口销毁
        // ...
        return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}</code>

二、全局变量

我们需要两个全局变量保存模块和主窗口的句柄:
<code class="lang-cpp">void* hInst; // 模块句柄
void* hMainWnd; // 主窗口句柄</code>

在wWinMain中,第一个参数提供了模块句柄,现在保存到全局变量:
<code class="lang-cpp">// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    hInst = hInstance; // 保存模块句柄
                       
    // ... 注册窗口类 建立窗口 显示和更新窗口 消息循环 ...
}</code>

三、注册窗口类

在Windows中,窗口的部分特性需要与窗口类(WNDCLASS)进行自定义。WNDCLASS是一个C语言结构体,我们需要填写它们的内容。
WNDCLASS {
    样式 函数(样式=窗口类样式,常用的有CS_HREDRAW和CS_VREDRAW;函数=窗口处理函数)
    类增 窗增(高级参数,表示窗口类和每个窗口附加数据的字节数,一般填0)
    模块 图标 光标(模块=模块句柄;图标=左上角图标;光标=常说的鼠标指针)
    背景 菜单 类名(背景=背景画刷;菜单=菜单资源标识,字符串或小于65536的整数;类名=窗口类的名称,随便写)
}
在这里如下填写,这些内容一般情况下是比较固定的:
<code class="lang-cpp">// 窗口类设定
// 样式、函数
// 类增、窗增            (高级参数)
// 模块、图标、光标     (光标=鼠标指针)
// 背景、菜单、类名
WNDCLASS wc = {
    CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
    WndProc, // 窗口消息处理函数
    0, 0,
    hInst, // 模块句柄
    LoadIcon(NULL, IDI_APPLICATION), // 图标:默认图标
    LoadCursor(NULL, IDC_ARROW), // 光标:箭头
    GetStockObject(WHITE_BRUSH), // 背景画刷:白色
    NULL, // 菜单:无
    L"MyWindow", // 窗口类名称
};</code>

填写完这个类之后,我们可以调用RegisterClass函数注册这个窗口类:
<code class="lang-cpp">if (!RegisterClass(&wc)) // 注册窗口类
    return 1;</code>

四、新建窗口

新建窗口需要使用CreateWindowEx函数,这个函数有12个参数,看似比较多,其实也不难记:
CreateWindowEx(
    扩展样式, 窗口类名, 窗口标题, 窗口样式, // 窗口样式常用WS_OVERLAPPEDWINDOW
    左, 上, 宽, 高, // 不想填写可以使用CW_USEDEFAULT
    父窗句柄, 菜单句柄, 模块句柄, 可选参数
)
不是所有参数都是必要的,部分参数可以填0或NULL。
在这里,我们这样建立一个标准的窗口:
<code class="lang-cpp">// 创建主窗口 - CreateWindowEx
// 扩展样式、窗口类名、窗口标题、窗口样式(样式=覆盖式窗口)
// 左、上、宽、高
// 父窗句柄、菜单句柄、模块句柄、可选参数
hMainWnd = CreateWindowEx(
    0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL, NULL, hInst, NULL);</code>

建立窗口以后,我们一般需要显示并更新窗口,这一步比较简单:
<code class="lang-cpp">ShowWindow(hMainWnd, nshow); // 显示窗口
UpdateWindow(hMainWnd); // 更新窗口</code>

五、进入消息循环

窗口显示以后,我们的程序并没有就此停住并继续接收窗口消息,因此我们要在wWinMain中不停地等待消息,并让系统进行处理。
其中GetMessage等待并获取消息,TranslateMessage将虚拟键码转换为Unicode字符,DispatchMessage将消息分发到处理函数
代码如下:
<code class="lang-cpp">MSG msg;
int rslt;
                       
// 消息循环 - 等待和处理各种消息
while (rslt = GetMessage(&msg, NULL, 0, 0)) // 获取消息
{
    if (rslt == -1) // 错误则退出
        return 1;
    TranslateMessage(&msg); // 将虚拟键码转换为Unicode字符
    DispatchMessage(&msg); // 分发消息到窗口消息处理函数
}</code>

六、退出消息循环

接收到一般消息时,GetMessage会返回TRUE,这时消息循环继续运行。当接收到WM_QUIT消息时,GetMessage会返回FALSE,程序将退出。退出代码保存在msg.wParam中:
<code class="lang-cpp">return (int)msg.wParam;</code>

在窗口被销毁时,我们期望消息循环退出,因此要向消息循环发送WM_QUIT消息,方法是调用PostQuitMessage(n)函数,n为退出码。在WndProc中添加以下代码:
<code class="lang-cpp">// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口销毁
        PostQuitMessage(0); // 发送WM_QUIT,退出消息循环
        return 0;
    }
    return DefWindowProc(hWnd, msg, wp, lp);
}</code>

七、运行效果


主窗口0.png

八、完整的程序
<code class="lang-cpp">// 不严格区分句柄,均定义为void*
#define NO_STRICT
                       
// 包含WinAPI定义
#include <windows.h>
                       
// 平台相关的整数类型:
// INT_PTR 16 -> 32 -> 64
// UINT_PTR 16 -> 32 -> 64
// LONG_PTR 32 -> 32 -> 64
// ULONG_PTR 32 -> 32 -> 64
                       
void* hInst; // 模块句柄
void* hMainWnd; // 主窗口句柄
                       
// 窗口消息处理函数 - WndProc
LONG_PTR __stdcall WndProc(void* hWnd, unsigned int msg, UINT_PTR wp, LONG_PTR lp)
{
    switch (msg) // 判断窗口消息的种类
    {
    case WM_DESTROY: // 窗口被销毁时
        PostQuitMessage(0); // 发送WM_QUIT,退出消息循环
        return 0;
    }
    // 如果消息未经处理,则由DefWindowProc处理
    return DefWindowProc(hWnd, msg, wp, lp);
}
                       
// 入口点 - wWinMain
int __stdcall wWinMain(void* hInstance, void* reserved, wchar_t* cmdline, int nshow)
{
    hInst = hInstance; // 保存模块句柄
                       
    // 窗口类设定
    // 样式、函数
    // 类增、窗增            (高级参数)
    // 模块、图标、光标     (光标=鼠标指针)
    // 背景、菜单、类名
    WNDCLASS wc = {
        CS_HREDRAW | CS_VREDRAW, // 样式:横向、纵向改变大小时重绘
        WndProc, // 窗口消息处理函数
        0, 0,
        hInst, // 模块句柄
        LoadIcon(NULL, IDI_APPLICATION), // 图标:默认图标
        LoadCursor(NULL, IDC_ARROW), // 光标:箭头
        GetStockObject(WHITE_BRUSH), // 背景画刷:白色
        NULL, // 菜单:无
        L"MyWindow", // 窗口类名称
    };
                       
    if (!RegisterClass(&wc)) // 注册窗口类
        return 1;
                       
    // 创建主窗口 - CreateWindowEx
    // 扩展样式、窗口类名、窗口标题、窗口样式(样式=覆盖式窗口)
    // 左、上、宽、高
    // 父窗句柄、菜单句柄、模块句柄、可选参数
    hMainWnd = CreateWindowEx(
        0, L"MyWindow", L"这是主窗口", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInst, NULL);
                       
    ShowWindow(hMainWnd, nshow); // 显示窗口
    UpdateWindow(hMainWnd); // 更新窗口
                       
    MSG msg;
    int rslt;
                       
    // 消息循环 - 等待和处理各种消息
    while (rslt = GetMessage(&msg, NULL, 0, 0)) // 获取消息
    {
        if (rslt == -1) // 错误则退出
            return 1;
        TranslateMessage(&msg); // 将虚拟键码转换为Unicode字符
        DispatchMessage(&msg); // 分发消息到窗口消息处理函数
    }
                       
    return (int)msg.wParam;
}</windows.h></code>
文号 / 783415

千古风流
名片发私信
学术分 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}}