调试一个 Duilib 界面程序时遇到一个问题:当在主函数里使用 ShowModal() 显示第一个对话框并关闭对话框后,再一次使用 ShowModal() 显示第二个对话框时第二个对话框便直接关闭退出了。

    CDemoWindow* pFrame1 = new CDemoWindow();
    pFrame1->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    pFrame1->CenterWindow();
    pFrame1->ShowModal();

    CDemoWindow* pFrame2 = new CDemoWindow();
    pFrame2->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
    pFrame2->CenterWindow();
    pFrame2->ShowModal();

调试了一下发现第二个对话框的消息循环很快收到一个 WM_QUIT 消息导致了它的退出。继续跟踪发现原来是第一个对话框的 OnDestory() 里调用了 PostQuitMesage() 往消息队列里发送了 WM_QUIT 消息,而 ShowModal() 里的消息循环的循环条件写的是

    while(::IsWindow(m_hWnd) && ::GetMessage(&msg, NULL, 0, 0))

它最终是因为 ::IsWindow(m_hWnd) 为 false 而退出消息循环的, MW_QUIT 消息依然被留在了消息队列中,随后被下一个对话框收到了。

修改方法就是去掉 OnDestroy() 中的 PostQuitMessage() 调用。因为窗口已经销毁,所以不用 MW_QUIT 也能正常退出消息循环。

另外对比了一下 MFC,当在 MFC 的应用程序向导中选择“基于对话框”作为应用程序类型后,并在代码中将 App::InitInstance() 作如下补充:

BOOL CMFCApplication2App::InitInstance()
{
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();
    AfxEnableControlContainer();
    CShellManager *pShellManager = new CShellManager;
    CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));

    SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
    {
        CMFCApplication2Dlg dlg;
        m_pMainWnd = &dlg;
        INT_PTR nResponse = dlg.DoModal();
    }
    {
        CMFCApplication2Dlg dlg;
        m_pMainWnd = &dlg;
        INT_PTR nResponse = dlg.DoModal();
    }
    if (pShellManager != NULL)
    {
        delete pShellManager;
    }

    // 由于对话框已关闭,所以将返回 FALSE 以便退出应用程序,
    //  而不是启动应用程序的消息泵。
    return FALSE;
}

即在第一个对话框后立即调用生成第二个对话框。结果发现第二个对话框的 DoModal() 也是立刻退出了。跟踪了一下代码发现在 RunModalLoop() 的消息循环里同样收到了 WM_QUIT。继续跟踪了一下,发现第一个对话框在收到最后一个消息 WM_NCDESTORY 后调用了 AfxPostQuitMessage(0),塞了个 MW_QUIT 到消息队列里。

但是如果在向导中不选择“基于对话框”类型的话,在产生主窗口前先产生一个或多个模态对话框则不会导致随后的主窗口退出。这个原因出在窗口对 WM_NCDESTORY 的处理上。

void CWnd::OnNcDestroy()
{
    // cleanup main and active windows
    CWinThread* pThread = AfxGetThread();
    if (pThread != NULL)
    {
        if (pThread->m_pMainWnd == this)    // 关键在这里
        {
            if (!afxContextIsDLL)
            {
                // shut down current thread if possible
                if (pThread != AfxGetApp() || AfxOleCanExitApp())
                    AfxPostQuitMessage(0);
            }
            pThread->m_pMainWnd = NULL;
        }
        if (pThread->m_pActiveWnd == this)
            pThread->m_pActiveWnd = NULL;
    }

    // 后面省略
    // ...
}

其中有一个当前窗口是否是作为当前线程的主窗口的判断,如果是的话才发送 WM_QUIT 消息。而基于对话框类型的程序的话则在对话框声明之后便将其作为线程主窗口记录下来了(对话框部分代码的 16 和 21 行),所以窗口销毁时会有额外的 WM_QUIT。