windows SDK编程 --- 消息(3)

前置知识

一、消息的分类

1. 鼠标消息

处理与鼠标交互相关的事件,比如移动、点击和滚动等。例如:

  • WM_MOUSEMOVE: 当鼠标在窗口客户区内移动时发送。
  • WM_LBUTTONDOWN: 当用户按下鼠标左键时发送。
  • WM_LBUTTONUP: 当用户释放鼠标左键时发送。
  • WM_RBUTTONDOWN: 当用户按下鼠标右键时发送。
  • WM_RBUTTONUP: 当用户释放鼠标右键时发送。
  • WM_MBUTTONDOWN: 当用户按下鼠标中键时发送。
  • WM_MBUTTONUP: 当用户释放鼠标中键时发送。
  • WM_MOUSEWHEEL: 当用户旋转鼠标滚轮时发送。

2. 键盘消息

  • WM_KEYDOWN: 当用户按下一个键盘键时发送,不包括系统键,如 Alt 和 F10。
  • WM_KEYUP: 当用户释放一个键盘键时发送。
  • WM_CHAR: 当按下的键产生了一个字符时发送,比如按下了 ‘A’。

3. 快捷键消息

  • WM_HOTKEY: 当用户按下注册的热键时发送。

4. 菜单消息

  • WM_COMMAND: 可以由菜单、加速器、控件发送,用来指示用户选择了哪个命令或操作。

5. 窗口消息

  • WM_CREATE: 在创建窗口时发送,通常用于初始化操作。
  • WM_CLOSE: 当窗口被请求关闭时发送,可以由用户操作或程序控制触发。
  • WM_DESTROY: 当窗口即将被销毁时发送。
  • WM_MOVE: 当窗口改变位置时发送。
  • WM_SIZE: 当窗口改变大小时发送。
  • WM_PAINT: 当客户区需要重绘时发送。

6. 控件消息

  • WM_HSCROLL: 当水平滚动条被滚动时发送。
  • WM_VSCROLL: 当垂直滚动条被滚动时发送。
  • WM_NOTIFY: 控件向父窗口发送通知消息,例如列表视图的项被点击。

7. 自定义消息

  • WM_USER + X: 开发者定义的消息,用于特定应用程序的自定义事件,其中 X 是一个大于等于 0 的整数。

8.系统消息

  • WM_ACTIVATE: 窗口被激活或停用时发送。
  • WM_SETFOCUS: 窗口获得焦点时发送。
  • WM_KILLFOCUS: 窗口失去焦点时发送。
  • WM_TIMER: 处理定时器消息,当定时器到期时发送。
  • WM_QUIT: 指示程序退出,通常由 PostQuitMessage 发出。

二、消息处理流程

在 Windows 程序中,消息处理的流程是通过一个称为消息循环的结构,结合窗口过程(WndProc)来完成的。这个窗口过程是一个由开发者实现的函数,用于响应和处理发送到窗口的消息。以下是详细的消息处理流程:

1.消息循环

  1. 程序入口(WinMain):程序启动后,在 WinMain 函数中初始化应用程序,并进入消息循环。
  2. 消息获取(GetMessage):通过调用 GetMessage 函数从消息队列中检索消息。这个函数会阻塞,直到队列中有消息。
  3. 消息翻译(TranslateMessage):一些消息,如键盘输入,可能需要翻译和预处理。TranslateMessage 函数会对这些消息进行适当处理,比如生成字符消息(WM_CHAR)。
  4. 消息分发(DispatchMessage):然后调用 DispatchMessage 函数,将消息传递给相应窗口的窗口过程进行处理。

2. 窗口过程 (WndProc)

  1. 定义窗口过程:在创建窗口时指定的窗口过程函数(通常称为 WndProc)。
  2. 处理消息:窗口过程接收到消息后,会使用一个 switch 语句(或类似的结构)来识别消息类型,并对每种类型的消息做出响应。
  3. 执行动作:根据消息类型,窗口过程会执行特定的动作。例如,WM_PAINT 消息会导致窗口重新绘制其内容,WM_DESTROY 消息会触发清理资源的操作。
  4. 默认处理:对于窗口过程未处理的消息,通常会调用 DefWindowProc 函数,这是系统提供的默认窗口过程,用于处理标准动作。

3. 关闭流程

  1. 接收 WM_CLOSE:当用户点击窗口关闭按钮或者通过其他方式请求关闭窗口时,WM_CLOSE 消息会被发送到窗口过程。
  2. 可选的处理:窗口过程可以选择如何响应 WM_CLOSE。如果程序需要确认用户的关闭请求(比如,询问是否保存数据),就可以在这里实现。
  3. 调用 DestroyWindow:如果窗口过程确认关闭,它会调用 DestroyWindow 函数来销毁窗口。
  4. 接收 WM_DESTROY:DestroyWindow 调用后,WM_DESTROY 消息会被发送到窗口过程。在这里,窗口过程通常会进行资源的最终清理。
  5. 发送 WM_QUIT:在 WM_DESTROY 的处理结束后,窗口过程会调用 PostQuitMessage 函数,这会放置 WM_QUIT 消息到消息队列。
  6. 结束消息循环:当 GetMessage```检索到 WM_QUIT`` 消息时,它会返回 0,导致消息循环结束,程序随之退出。

三、WM_CLOSE 和 WM_DESTROY的使用时机

WM_CLOSE

目的与时机:
WM_CLOSE 消息是当用户点击窗口的关闭按钮,或者通过程序调用 CloseWindow() 函数时发送的。这个消息是一个请求,指示应用程序应当开始关闭窗口的过程。
处理:
接收到 WM_CLOSE 消息后,窗口过程可以选择执行多种操作,例如提示用户保存未保存的工作、清理资源或直接关闭。如果决定继续关闭窗口,窗口过程通常会调用 DestroyWindow() 函数来销毁窗口。
可选性:
WM_CLOSE 是可拦截和可忽略的。应用程序可以根据用户的选择或其他条件决定是否响应 WM_CLOSE,或者如何响应。

WM_DESTROY

目的与时机:
WM_DESTROY 消息是在窗口实际被销毁前发送的。当 DestroyWindow() 函数被调用时,该函数先处理所有必要的资源释放,然后发送 WM_DESTROY 消息给窗口过程。
处理:
接收到 WM_DESTROY 消息时,窗口过程应该处理所有最终的清理工作,如释放内存、保存状态等。处理完成后,通常会调用 PostQuitMessage() 函数来发送 WM_QUIT 消息,这会导致消息循环结束,进而使应用程序退出。
不可忽略:
WM_DESTROY 是窗口生命周期的自然部分,它表示窗口即将被销毁,不应被忽略或拦截,以确保资源得到正确释放。

总结:

  • WM_CLOSE 可以视为关闭过程的开始,是一个可以被程序控制和决定的阶段,而 WM_DESTROY 则标志着关闭过程的终结,窗口即将被销毁,此时应进行最后的清理工作。
  • WM_CLOSE 可以看作是一个警告或询问(即将关闭,是否继续?),而 WM_DESTROY 是确认(窗口正在被销毁)。


四、CreateWindowEx()

调用 CreateWindowEx() 函数会触发一系列窗口消息,这些消息涉及到窗口的创建和显示过程。以下是在调用 CreateWindowEx() 时通常会触发的主要消息,按照它们出现的典型顺序列出:

1. WM_GETMINMAXINFO

  • 这是创建过程中收到的第一个消息。
  • 在窗口创建期间,此消息请求窗口的最小和最大尺寸限制。
  • 这个消息可以用于限制窗口的大小或提供特定的尺寸限制。

2. WM_NCCREATE

  • 此消息在窗口的非客户区(如标题栏、边框等)被创建前发送。
  • 它用于准备窗口创建,如果窗口过程返回 FALSE,窗口创建将立即停止。

3. WM_NCCALCSIZE

  • 当系统或另一个应用程序发送大小和位置改变请求时,发送此消息。
  • 它用于计算窗口的客户区大小,即实际显示内容的区域。

4. WM_CREATE

  • 在窗口的客户区和非客户区都创建好之后发送。
  • 此消息通常用于执行初始化任务,如创建子控件、分配资源等。
  • 如果窗口过程返回 -1,则窗口创建将被终止,窗口也将被销毁。

五、调试技巧

1. 监视

在 Visual Studio 中使用 消息变量名,wm 来在监视窗口中查看 Windows 消息名称是一个非常方便的功能,它可以帮助开发者直观地理解当前处理的消息类型。

在这里插入图片描述

2. 调试输出函数

1.为什么不能用printf?
  • 一般开发 Windows GUI 应用程序并不附带控制台窗口,printf 只能向控制台输出文本,但如果没有控制台这些输出就无处显示。
2. OutputDebugString函数:
  • OutputDebugString 是 Windows API 中的一个调试辅助函数,它用于将字符串信息发送到调试器的输出窗口。这对于开发和调试 Windows 应用程序非常有用,尤其是当你需要在运行时监控程序的内部状态或行为时。
case WM_LBUTTONDOWN:
    {
        int x = (int)(short)LOWORD(lParam);  // 新的 X 坐标
        int y = (int)(short)HIWORD(lParam);  // 新的 Y 坐标
        char buffer[100];
        //将格式化的数据写入到buffer缓冲区中
        wsprintf(buffer, "你点击了,x坐标:%d, y坐标:%d\n", x, y);
        //输出调试信息
        OutputDebugString(buffer);
        return 0;
    }
3. 捕捉输出调试信息
  • 使用vs2019查看OutputDebugString输出的调试信息
    在这里插入图片描述
  • 使用 DebugView 来捕捉由 OutputDebugString 输出的调试信息。点击下载
  • 在 DebugView 中,确保勾选 Capture 菜单下的 Capture Global Win32 选项,这允许 DebugView 捕捉所有进程的输出,而不仅仅是当前用户会话的进程。
  • 注意:当你使用 Visual Studio 2019 或任何其他 IDE 调试程序时,可能会遇到 DebugView 无法捕捉到由 OutputDebugString 输出的调试信息的情况。这通常是因为调试器已经接管了调试输出。你需要在外部直接打开exe。

在这里插入图片描述

示例代码

#include <windows.h>

//显示错误信息
void ShowErrorMessage();




//5.消息处理
LRESULT CALLBACK WndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    // 根据消息类型进行分支处理

    switch (msg)
    {
    case WM_CREATE:
    {
        OutputDebugString("WM_CREATE\n");
        return 0;
    }


    case WM_CLOSE:
    {
        //询问是否关闭窗口
        int id = MessageBox(NULL, "你确定关闭窗口吗", "WM_CLOSE", MB_YESNO);
        if (id == IDYES)
        {
            DestroyWindow(hwnd);
        }

        return 0;
    }


    case WM_DESTROY:
    {
        OutputDebugString("WM_DESTROY\n");

        PostQuitMessage(0);
        return 0;

    }


    case WM_MOVE:      //窗口移动
    {
        int x = (int)(short)LOWORD(lParam);  // 新的 X 坐标
        int y = (int)(short)HIWORD(lParam);  // 新的 Y 坐标
        char buffer[100];
        wsprintf(buffer,"窗口移动到了:x坐标:%d, y坐标:%d\n",x,y);
        OutputDebugString(buffer);
        break;

    }

    case WM_LBUTTONDOWN:    //鼠标左键按下
    {
        int x1 = (int)(short)LOWORD(lParam);  // 新的 X 坐标
        int y1 = (int)(short)HIWORD(lParam);  // 新的 Y 坐标
        char buffer1[100];
        wsprintf(buffer1, "鼠标点击了,x坐标:%d, y坐标:%d\n", x1, y1);
        OutputDebugString(buffer1);


        return 0;
    }

    }

    return DefWindowProc(hwnd, msg, wParam, lParam);


}



int WINAPI WinMain(
    HINSTANCE hInstance,     // 当前实例的句柄
    HINSTANCE hPrevInstance, // 前一个实例的句柄,现在总是为 NULL
    LPSTR lpCmdLine,         // 命令行参数的字符串
    int nCmdShow             // 指示程序窗口应如何被显示
)
{
    //1.注册窗口
    char MyWindowClassName[] = "MyWindowClass";         //窗口类名
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_VREDRAW | CS_HREDRAW;     //
    wc.lpfnWndProc = WndProc;    //窗口过程函数(窗口回调函数->处理消息)
    wc.hInstance = hInstance;
    wc.hIcon = NULL;             //图标
    wc.hCursor = NULL;          //光标
    wc.hbrBackground = CreateSolidBrush(RGB(0, 255, 0));    //窗口背景颜色刷子
    wc.lpszMenuName = NULL;     //菜单名称
    wc.lpszClassName = MyWindowClassName;    //窗口类名


    if (RegisterClassEx(&wc) == 0)
    {
        ShowErrorMessage();
        return 0;
    }




    //2.创建窗口
    char MyWindowName[] = "MyWindowClass";         //窗口名称

    HWND hwnd = CreateWindowEx(
        0,
        MyWindowClassName,
        MyWindowName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        NULL
    );
    if (hwnd == NULL)
    {
        ShowErrorMessage();
        return 0;
    }



    //3.显示更新窗口

    ShowWindow(hwnd, SW_SHOWNORMAL);
    UpdateWindow(hwnd);

    //4.消息循环(消息队列)
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return 0;
}





void ShowErrorMessage()
{
    char szErrorMessage[512];  // 错误消息缓冲区

    DWORD dwResult = FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL,
        GetLastError(),
        0,
        szErrorMessage,
        sizeof(szErrorMessage) / sizeof(WCHAR),
        NULL);

    if (dwResult > 0) {
        // 使用 MessageBox 显示错误消息
        MessageBox(NULL, szErrorMessage, "Error", MB_OK | MB_ICONERROR);
    }
    else {
        // 如果无法获取错误消息,使用 MessageBox 显示一个失败通知
        MessageBox(NULL, "Failed to retrieve the error message.", "Error", MB_OK | MB_ICONERROR);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/565819.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[2024更新]如何从Android恢复已删除的相机照片?

相信大家都经历过Android手机误删相机图片的经历。您是否正在寻找一种可行的方法来挽救这些丢失的照片&#xff1f;如果这是你迫切想解决的问题&#xff0c;那么这篇文章绝对可以帮助你。然而&#xff0c;与其考虑如何从Android恢复已删除的相机照片&#xff0c;我们更愿意建议…

激光雷达(LiDAR)面临的主要问题与挑战

本文讨论目前激光雷达在汽车、机器人以及无人机等场景应用时面临的一些问题和挑战,包括成本、尺寸、系统复杂性、杂散反射、续航,以及安全性等方面。 成本 一直以来,激光雷达的成本都是影响其广泛应用的关键因素。从最早的上万美元一颗,经过近十年的发展,激光雷达的价格…

20240331-1-基于深度学习的模型

基于深度学习的模型 知识体系 主要包括深度学习相关的特征抽取模型&#xff0c;包括卷积网络、循环网络、注意力机制、预训练模型等。 CNN TextCNN 是 CNN 的 NLP 版本&#xff0c;来自 Kim 的 [1408.5882] Convolutional Neural Networks for Sentence Classification 结…

网络安全数字孪生:一种新颖的汽车软件解决方案

摘要 随着汽车行业转变为数据驱动的业务&#xff0c;软件在车辆的开发和维护中发挥了核心作用。随着软件数量的增加&#xff0c;相应的网络安全风险、责任和监管也随之增加&#xff0c;传统方法变得不再适用于这类任务。相应的结果是整车厂和供应商都在努力应对汽车软件日益增加…

C++及QT的线程学习

目录 一. 线程学习 二. 学习线程当中&#xff0c;得到的未知。 1. 了解以下MainWindow和main的关系 2. []()匿名函数 有函数体&#xff0c;没有函数名. 3. join和detach都是用来管理线程的生命周期的&#xff0c;它们的区别在于线程结束和资源的回收。 4. operator()() 仿…

论文略读:OpenGraph: Towards Open Graph Foundation Models

arxiv 2023 1 intro Graph大模型希望OpenGraph能够捕捉通用的拓扑结构模式&#xff0c;对测试数据进行Zero-shot预测 仅通过前向传播过程&#xff0c;就可以对测试图数据进行高效的特征提取和准确预测模型的训练过程在完全不同的图数据上进行&#xff0c;在训练阶段不接触测试…

CSS3新增特性(一)

目录 一、CSS3 新增选择器 1. 子级选择器 2. 兄弟选择器 相邻兄弟选择器 其他兄弟选择器 3. 结构伪类选择器 ① E:first-child ② E:last-child ③ nth-child&#xff08;n&#xff09; n为数字&#xff1a; n为关键字&#xff1a; n为公式&#xff1a; ④ E: firs…

visionTransformer window平台下报错

错误&#xff1a; KeyError: Transformer/encoderblock_0/MlpBlock_3/Dense_0kernel is not a file in the archive解决方法&#xff1a; 修改这个函数即可&#xff0c;主要原因是Linux系统与window系统路径分隔符不一样导致 def load_from(self, weights, n_block):ROOT f&…

【RT-Thread应用笔记】FRDM-MCXN947上的RW007实践——WiFi延迟和带宽测试

【RT-Thread应用笔记】FRDM-MCXN947上的RW007实践——WiFi延迟和带宽测试 一、背景介绍1.1 RW007模组简介1.2 Arduino接口简介1.3 RW007软件包简介1.4 RT-Thread env工具简介 二、创建工程2.1 新建工程2.2 添加rw007软件包2.3 打开RW007配置项2.4 启用pin驱动2.5 禁用rw007的ST…

Cloud微服务:Ribbon负载均衡

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Ribbon负载均衡 一、Ribbon - 负载均衡原理、流…

探索 虚拟化技术+Docker部署与操作

目录 一、你知道哪些云 1.1国内云 1.2国外云 二、Iaas、 Paas、SaaS三种云服务区别 2.1第一层叫做IaaS 2.2第二层就是所谓的PaaS 2.3第三层也就是所谓SaaS 三、虚拟化架构 3.1寄居架构 3.2源生架构 3.3操作系统虚拟化架构 3.4混合虚拟化架构 四、虚拟化特点及优势…

jmeter5.4.1源码编译(IDEA)问题解决

问题现象&#xff1a;最近想更深入的研究下jmeter5.4.1的原理及功能具体实现&#xff0c;从官网down了个源码&#xff0c;在本地使用IDEA工具导入项目、编译时&#xff0c;报以下错误&#xff1a; class jdk.internal.loader.ClassLoaders$PlatformClassLoader cannot be cast…

vue整合Echarts

首先打开网址https://echarts.apache.org/examples/zh/index.html 进入Echars官网找到自己想要的图形我这里选择的是柱形图 点开完整代码直接cv大法 下载Echars的npm npm install echarts 在vue里面挂在个div 导入相关包 写个方法 就是cv过来的 然后改成后端传过来的值…

【STM32+HAL+Proteus】系列学习教程---RS485总线(收发仿真实现)

实现目标 1、掌握UART/USART/RS485等几个常见概念的区别 2、掌握RS485的逻辑电平、硬件接线等基础知识 3、具体实现目标&#xff1a;1、利用两个单片机组成RS485通信网络&#xff1b;2、两个单片机之间能实现正常收发数据。 一、串口、RS485等之间的关系 串口&#xff1a;是…

微机原理实验三、将AX寄存器中的16位数分成4组,每组4位,让后把这四组数分别放在AL,BL,CL,DL

微机原理实验三、将AX寄存器中的16位数分成4组&#xff0c;每组4位&#xff0c;让后把这四组数分别放在AL,BL,CL,DL 功能&#xff1a; 将AX寄存器中的16位数分成4组&#xff0c;每组4位&#xff0c;让后把这四组数分别放在AL,BL,CL,DL ; 调试结果&#xff1a; input&#xff1a…

ASP.NET集成客户关系管理的企业网站的设计与开发

摘 要 企业要在激烈的市场竞争中立于不败之地&#xff0c;就必须找一种全新的管理理念和管理手段&#xff0c;对其内部和外部资源进行有效的整合。新一代ERP产品正在向客户端和供应端延伸&#xff0c;客户端的延伸即是客户关系管理。对于每个企业来说客户管理的完善程度将直接…

计算机网络 --- WebSocket协议 和 Signalr

计算机网络 --- WebSocket协议 和 Signalr 什么是WebSocket什么是SignalrSignalr Example -- SimpleChat 什么是WebSocket HTTP是基于TCP协议的&#xff0c;同一时间里&#xff0c;客户端和服务器只能有一方主动发数据&#xff0c;是半双工通信。 通常&#xff0c;打开某个网页…

Qt笔记-解决子控制大小获取不正确(width和height)需要重制窗体后,才能获得正确的值

在Qt中&#xff0c;子控件的宽度和高度在构造后并不准确&#xff0c;而只有在调整窗口大小后才正确&#xff0c;这可能是因为子控件的布局或者约束尚未完全计算和应用。 为了解决这个问题&#xff0c;可以使用QTimer来延迟获取子控件的宽度和高度&#xff0c;以确保在布局和约…

ffmpeg初体验

一&#xff1a;安装 sudo yum install epel-release -y sudo yum update -ysudo rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpmyum -y install …

数据结构(邓俊辉)学习笔记——向量vector_02_动态空间管理

文章目录 1. 概述2. 静态空间管理缺点3. 动态空间管理3.1 扩容3.1.1 如何实现扩容3.1.2 扩容算法3.1.3 容量递增策略 VS 容量倍增策略3.1.3.1 容量倍增策略分摊分析3.1.3.2 容量递增策略分摊分析3.1.3.3 结果对比 3.2缩容3.2.1 动态缩容算法实现3.2.2 动态缩容算法时间复杂度 4…