MFC中关闭对话框的一些逻辑

MFC中关闭对话框的一些逻辑


一.Win32下关闭消息的处理逻辑

先介绍一下Win32编程下消息是怎么处理的

在Win32编程中,消息是通过以下步骤处理的(官方解释):

消息循环:对于每个创建窗口的线程,操作系统都会为窗口消息创建一个队列。这个队列保存了该线程上创建的所有窗口的消息。你的程序需要一个循环来检索这些消息并将它们分发到正确的窗口。你可以通过调用GetMessage函数来从队列中获取一个消息

窗口过程:每个窗口都有一个窗口过程,当窗口收到消息时,系统会调用这个窗口过程。窗口过程负责处理窗口收到的所有消息。例如,当用户点击鼠标或按下键盘时,窗口过程会收到一个消息,并负责处理这个消息。

消息处理:当窗口过程收到一个消息时,它会根据消息的类型来决定如何处理这个消息。例如,如果窗口过程收到一个WM_PAINT消息,那么它就知道窗口的客户区已经改变,需要重新绘制

细说消息循环

在Win32编程中,消息循环是一个核心的概念。它是一个无限循环,用于从应用程序的消息队列中获取消息,并将这些消息分发到相应的窗口。

每个基于窗口的应用程序都有一个消息队列,这个队列保存了应用程序的所有窗口的消息。这些消息包括用户的输入(例如鼠标点击和键盘按键),以及系统的通知(例如窗口需要重绘,或者窗口的大小已经改变)。

消息循环的工作方式如下:

  1. 获取消息:消息循环首先调用GetMessage函数,从消息队列中获取一个消息。如果队列为空,那么GetMessage函数会阻塞,直到有新的消息被加入到队列中。
  2. 翻译消息:然后,消息循环调用TranslateMessage函数,对某些键盘消息进行转换。例如,TranslateMessage函数可以将键盘消息转换为字符消息。
  3. 分发消息:最后,消息循环调用DispatchMessage函数,将消息分发到相应的窗口。DispatchMessage函数会调用窗口的窗口过程,并将消息作为参数传递给窗口过程。

简单的消息循环的示例:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

在这个示例中,while循环会一直运行,直到GetMessage函数返回0,表示接收到了WM_QUIT消息。这通常发生在用户关闭应用程序的主窗口,或者应用程序调用PostQuitMessage函数时。

需要注意的是,虽然这个示例展示了一个基本的消息循环,但实际的应用程序可能需要一个更复杂的消息循环,以处理更多的消息和事件。

一个Win32下处理消息的例子

当用户点击取消按钮时,操作系统会生成一个WM_COMMAND消息,并将其发送到应用程序的消息队列. (这是操作系统自动发送的, 不是SendMessage发送的, 如果你想模拟用户点击或在不同窗口之间传递命令, 可以使用SendMessagePostMessage函数来手动发送WM_COMMAND消息)

WM_COMMAND消息是什么消息?

[

WM_COMMAND 是一种消息类型,它通常在用户与界面元素(如按钮、菜单项等)交互时发送。WM_COMMAND 消息的参数中包含了大量的有用信息,例如,wParam 参数的低字节包含了命令ID,这可以用来区分是哪个按钮被点击。lParam 参数包含了发送命令消息的子窗口(即按钮)的句柄,这可以用来获取或修改按钮的状态。

]

然后,应用程序的消息循环会从消息队列中获取这个消息,然后可能TransLate, 然后Dispatch分发给对应窗口

最后,操作系统会调用窗口的窗口过程(即MyWindowProc回调函数),并将消息等作为参数传递给窗口过程。

MyWindowProc函数中,我们首先检查消息是否是WM_COMMAND消息。如果是,那么我们进一步检查wParam参数的低字是否等于取消按钮的ID IDCANCEL。如果是,那么我们就知道用户点击了取消按钮。然后,用SendMessage发送一个WM_CLOSE消息到应用程序的消息队列

消息循环又会在消息队列里捕获到WM_CLOSE消息, 然后可能TransLate, 然后Dispatch分发给SendMessage第一个参数即窗口句柄中指定的对应窗口的窗口过程函数

(**窗口过程函数MyWindowProc是我们自己定义的, SendMessage知道窗口句柄, 咋就能知道它对应的窗口过程呢?**因为创建一个窗口时,需要提供一个窗口过程, 也可以通过SetWindowLong函数来更改窗口过程. )

当窗口过程收到WM_CLOSE消息时,调用DestroyWindow函数来销毁窗口。

DestroyWindow都干了什么:

  1. 发送消息:DestroyWindow函数首先会向窗口发送WM_DESTROY和WM_NCDESTROY消息
  2. 销毁一些资源

当DestroyWindow函数被调用时,它首先会向窗口发送WM_DESTROY消息。这个消息是在窗口被销毁之前发送的,此时窗口的所有子窗口仍然存在。窗口过程可以在处理WM_DESTROY消息时进行一些清理工作,例如释放与窗口关联的资源。

在所有子窗口被销毁之后,DestroyWindow函数会向窗口发送WM_NCDESTROY消息。这个消息是在窗口被销毁之后发送的,此时窗口的所有子窗口已经不存在了。窗口过程可以在处理WM_NCDESTROY消息时进行一些后处理工作,例如释放窗口过程自己分配的内存。WM_DESTROY和WM_NCDESTROY消息都是由系统自动发送的,你的代码不需要显式地发送这些消息

当窗口被销毁后,发送一个WM_QUIT消息来结束消息循环。

LRESULT CALLBACK MyWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_COMMAND:
        // 当用户点击取消按钮时,我们会收到一个WM_COMMAND消息
        // 我们可以检查wParam参数的低字是否等于取消按钮的ID
        if (LOWORD(wParam) == IDCANCEL)
        {
            // 如果用户点击了取消按钮,那么我们发送一个WM_CLOSE消息来关闭窗口
            SendMessage(hwnd, WM_CLOSE, 0, 0);
        }
        break;

    case WM_CLOSE:
        // 当我们收到WM_CLOSE消息时,我们调用DestroyWindow函数来销毁窗口
        DestroyWindow(hwnd);
        break;

    case WM_DESTROY:
        // 当窗口被销毁时,我们发送一个WM_QUIT消息来结束消息循环
        PostQuitMessage(0);
        break;

    default:
        // 对于我们没有处理的消息,我们调用DefWindowProc函数来进行默认处理
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}

总结一下就是:

  1. 用户点击取消按钮, 操作系统自动发送WM_COMMAND 且附带按钮ID信息, 即IDCANCEL

  2. 消息循环从消息队列中获取WM_COMMAND 消息, 即GetMessge, 然后翻译, 即TranslateMessage(可能), 然后转发到对应窗口, 即DispatchMessage

  3. 操作系统会自动调用窗口过程(回调函数), 即将消息以及可能的数据作为参数交给窗口过程

  4. 窗口过程里对消息进行判断, 判断是WM_COMMAND, 且消息携带的参数是IDCANCEL后, 使用SendMessage(hwnd, WM_CLOSE, 0, 0); 发送WM_CLOSE给窗口, 这个WM_CLOSE消息不一定要自己手动发送, 一般调用 DestroyWindow 函数时,系统会自动向窗口发送 WM_CLOSE 消息, 无论哪种方式,WM_CLOSE 消息都是通知窗口即将被关闭的信号。

  5. 消息循环获取WM_CLOSE, Translate, Dispatch , 操作系统再次调用窗口过程, WM_CLOSE消息又来到了窗口过程

  6. 窗口过程识别了WM_CLOSE, 并调用相应的消息处理函数DestroyWindow来销毁窗口

  7. DestroyWindow在摧毁窗口前首先会自动向窗口发送WM_DESTROY消息 (此时窗口的所有子窗口仍然存在, 窗口过程可以在处理WM_DESTROY消息时进行一些清理工作,例如释放与窗口关联的资源) .WM_DESTROY消息会来到了窗口过程且被switch接住了, 调用PostQuitMessage, 这个函数会将一个 WM_QUIT 消息放入应用程序的消息队列。WM_QUIT 消息传递到窗口过程里会被DefWindowProc默认处理,导致 GetMessagePeekMessage 函数返回零,从而结束应用程序的消息循环。这通常意味着应用程序即将退出. 即启动销毁

  8. 在所有子窗口被销毁之后,DestroyWindow函数会在摧毁窗口后自动向窗口发送WM_NCDESTROY消息。WM_NCDESTROY消息会在窗口过程里被DefWindowProc默认处理, 此时窗口的所有子窗口已经不存在了。窗口过程可以在处理WM_NCDESTROY消息时进行一些后处理工作,例如释放窗口过程自己分配的内存。

  9. 当窗口被销毁后,发送一个WM_QUIT消息, WM_QUIT消息会在窗口过程里被DefWindowProc默认处理来结束消息循环。

再简化一下:

  1. WM_COMMAND (IDCANCEL)触发WM_CLOSE的发送(其实调用DestroyWindow通常会自动发送一个WM_CLOSE消息)
  2. WM_CLOSE消息是一个通知,表示窗口即将被销毁, 在处理它时, 可以选择阻止销毁
  3. 如果没有被阻止, DestroyWindow 会在窗口被销毁前发送 WM_DESTROY消息, 接收到WM_DESTROY消息时, 所有子窗口还都在, 这个阶段已经阻止不了销毁了, 但现在可以进行清理工作, 开始调用PostQuitMessage, PostQuitMessage发送WM_QUIT导致消息循环结束, 开始销毁
  4. 窗口和子窗口被销毁后, DestroyWindow 发送WM_NCDESTROY消息, 此阶段, 可以释放与窗口有关的任何内存对象

再再简化一下:

用户如果想关闭窗口, 一定会触发WM_CLOSE消息, 这个时候可以阻止销毁, WM_CLOSE意味着要关闭窗口

窗口销毁前会发送WM_DESTROY消息, 可以进行清理工作

窗口销毁后会发送WM_NCDESTROY消息, 可以释放与窗口有关的任何内存对象

二.MFC下的关闭消息处理逻辑

MFC是WIN32的高级封装, 基本都大差不差

MFC退出对话框的三种途径:

途径一:点击IDOK按钮退出:

首先

当用户点击IDOK按钮时,Windows会向对话框对象发送一个带按钮ID(IDOK)的BN_CLICKED控件通知消息。这将触发 OnOK() 函数。这个函数是 CDialog 类的成员函数,它的默认处理程序会调用 EndDialog 成员函数以关闭对话框窗口

BN_CLICKED是什么?

在 Win32 编程中,BN_CLICKED是一个通知消息, 主要就起到一个通知的作用, 且只能通知给父窗口. 注意, 它不是反射型消息. BN是BUTTON NOTIFICATION 的简写, 即按钮通知

日常使用就把它看作通知消息

严格意义上来讲, BN_CLICKED 是一个通知代码,它表示用户单击了一个按钮控件。当用户单击按钮时,按钮控件会发送 WM_LBUTTONDOWN 和 WM_LBUTTONUP 消息,然后发送 BN_CLICKED 通知消息给按钮的父窗口。这个通知代码是通过 WM_COMMAND 消息发送的。也就是说,WM_COMMAND 消息的 wParam 参数的高字节会包含 BN_CLICKED 通知代码,低字节会包含按钮的 ID,lParam 参数会包含按钮的句柄。所以,BN_CLICKED 是作为 WM_COMMAND 消息参数的一部分被发送的, 但一般就将BN_CLICKED视为通知消息

反射型消息又是什么?

在 Win32 编程中,反射型消息是指当一个控件(如按钮)发送一个消息到其父窗口,而父窗口又将这个消息“反射”回控件自己来处理的情况。这种机制允许控件自己处理某些类型的消息,而不需要父窗口进行处理。

为什么消息BN_CLICKED会触发OnOk()函数?

跟Win32中的消息处理机制大差不差,(但也会有点更复杂)

无非就是用户点击了IDOK, 发出BN_CLICKED消息(其实是WM_COMMAND消息带着BN_CLICKED通知消息), 进入消息队列, 消息循环从消息队列中取出消息, 进行翻译, 分发给操作系统, 操作系统根据消息的类型和目标窗口调用相应的窗口过程(回调函数), 窗口过程会调用成员函数OnOk()(事件处理函数, 只不过OnOk被默认重写了)

OnOK函数是什么函数?

OnOk函数是CDialog类中的一个虚函数, 当创建一个对话框类(CDialog类的派生类), 时,OnOK()函数会在这个派生类中被默认重写, 默认重写的函数体是CDialog::EndDialog(IDOK), 即关闭对话框, 当然可以自己重写

然后, 由于调用了CDialog::EndDialog(IDOK), 所以接下来对话框会开始销毁过程:

具体怎么销毁对话框对象的?

当调用CDialog::EndDialog(IDOK)函数来结束对话框时:

首先这个函数会结束对话框的模态循环,并设置一个标志,然后返回控制权给系统。系统在尝试从应用程序队列检索下一条消息之前,会检查这个结束标志。如果系统检查到已经设置了结束标志,那么系统会结束消息循环,并开始销毁对话框。在这个过程中,系统会在窗口即将被销毁前自动发送WM_DESTROY消息, 窗口过程针对WM_DESTROY消息调用消息处理函数OnDestroy()

什么是模态循环(模态对话框消息循环)?

其实就是MFC对Win32消息循环的更高级别的封装,当你打开一个模态对话框时,比如通过DoModal()函数,对话框会进入一个消息循环。在这个消息循环中,对话框会持续接收和处理用户的输入,比如点击按钮、输入文本等。

OnDestroy干了什么?

OnDestroy()函数通常被用来进行一些清理工作,如释放在对话框中使用的资源. 例如,如果你在对话框中创建了一些动态分配的内存,那么你可以在OnDestroy()函数中释放这些内存。

接着系统会继续销毁窗口

然后 系统会发送WM_NCDESTROY消息, 这个消息是在窗口被销毁后发送的,此时窗口已经从屏幕上删除.

窗口过程在处理WM_NCDESTROY消息的过程中,会调用PostNcDestroy()函数。这是在对话框被销毁后最后被调用的函数。在这个函数中,通常会删除对话框对象本身, 即 delete this. 这是因为在MFC中,对话框对象通常是用new来创建的,所以需要在对话框关闭后手动删除以防止内存泄漏。

最后,EndDialog会设置对话框的返回值为IDOK。这个返回值会被DoModal()函数返回,所以你可以在调用DoModal()的地方知道对话框是如何被关闭的。

总结一下就是:

  1. 用户点击IDOK按钮, 按钮发送通知消息BN_CLICKED

  2. 消息循环取出消息, 翻译, 分发给操作系统

  3. 操作系统根据消息的类型和目标窗口调用相应的窗口过程

  4. 窗口过程调用相应的处理函数OnOk()

  5. OnOk()中执行CDialog::EndDialog(IDOK)

  6. EndDialog会启动销毁窗口, 会设置结束标志

  7. 系统检测到结束标志后结束模态循环, 在窗口销毁前发送WM_DESTROY消息, 消息处理函数OnDestroy处理

  8. OnDestroy进行清理工作

  9. 系统继续销毁窗口

  10. 销毁后系统自动发送WM_NCDESTROY消息, 消息处理函数PostNcDestroy()处理

  11. PostNcDestroy会删除对话框对象本身

  12. 控制权会到EndDialog, EndDialog设置返回值为IDOK ,进而被DoModal返回

再简化一下:

  1. 用户点击IDOK按钮, 按钮发送通知消息给BN_CLICKED

  2. 窗口过程调用OnOk()处理

  3. 处理过程中操作系统会在窗口销毁前发送WM_DESTROY消息, 窗口过程调用OnDestroy()处理

  4. 操作系统销毁窗口

  5. 处理过程中操作系统会在窗口销毁后发送WM_NCDESTROY消息, 窗口过程调用PostNcDestroy()处理

途径二:点击IDCANCEL按钮退出

流程基本和点击IDOK按钮差不多

只不过用户点击按钮后发送BN_CLICKED通知消息时携带的按钮ID是IDCANCEL

然后窗口过程处理BN_CLICKED消息调用的函数是OnCancel

OnCancel里面是CDialog::EndDialog(IDCANCEL)

其他的就是一样的了

途径三:点击右上角的关闭按钮退出

其实也是大差不差:

  1. 用户点击右上角的关闭按钮, Windows会向对话框发送一个WM_CLOSE消息
  2. 窗口过程处理WM_CLOSE消息, 调用消息处理函数OnClose()
  3. OnClose()函数中会调用DestroyWindow()函数
  4. DestroyWindow()函数还是老样子, 销毁前后发送WM_DESTROY, WM_NCDESTROY
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值