• 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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    3. 捕捉输出调试信息
    • 使用vs2019查看OutputDebugString输出的调试信息
      在这里插入图片描述
    • 使用 DebugView 来捕捉由 OutputDebugString 输出的调试信息。点击下载
    • 在 DebugView 中,确保勾选 Capture 菜单下的 Capture Global Win32 选项,这允许 DebugView 捕捉所有进程的输出,而不仅仅是当前用户会话的进程。
    • 注意:当你使用 Visual Studio 2019 或任何其他 IDE 调试程序时,可能会遇到 DebugView 无法捕捉到由 OutputDebugString 输出的调试信息的情况。这通常是因为调试器已经接管了调试输出。你需要在外部直接打开exe。

    在这里插入图片描述

    示例代码

    #include 
    
    //显示错误信息
    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
  • 相关阅读:
    【WSL2】CENTOS7 安装与配置
    前端——html面试题(一)
    联邦学习(Federated Learning):技术角度的理解
    Qt 目录操作(QDir 类)及展示系统文件实战 & QFilelnfo 类介绍和获取文件属性项目实战
    手把手教你在VM虚拟机上安装windows11
    『现学现忘』Git后悔药 — 28、版本回退git reset --soft命令说明
    【TCP连接的状态】
    具有多孔光纤的偏振分束器
    生命 周期
    从android.graphics.Path中取出Point点,Kotlin
  • 原文地址:https://blog.csdn.net/qq_17111397/article/details/137846749