首页 > 解决方案 > 如何在另一个线程中关闭 MFC 模态对话框并获取对话框返回值?

问题描述

我有一个 MFC 对话框项目。主对话框中有一个下载按钮,点击它会提示一个进度条并开始下载。下载完成后,我希望它自动关闭。

void CProgressBarTest::DoDataExchange(CDataExchange* pDX)
{
    static auto funDownload = [&]() {
        m_downloadRetValue = ::SomeDownloadAPI(funDownloadCallback);

        //When download finished, close the current dialog (progress bar). Here are two options:
        //EndDialog(IDYES); // causes crash
        //::PostMessage(this->GetSafeHwnd(), WM_CLOSE, 0, 0);// doesn't crash, but without return valud IDYES.
    };

    m_thDownload = std::thread(funDownload);
}

以下是关闭进度条的两种方法:

EndDialog(IDYES): 导致崩溃。

::PostMessage(this->GetSafeHwnd(), WM_CLOSE, 0, 0):它可以关闭窗口而不会崩溃,但也没有返回值(IDYES)。

我想在外面做这样的检查,

void CGUIThreadTestDlg::OnBnClickedButton3()
{
    CProgressBarTest dlg(this);
    INT_PTR nRet = dlg.DoModal();
    switch (nRet)
    {
    case -1:
        AfxMessageBox(_T("Dialog box could not be created!"));
        break;
    case IDYES:
        AfxMessageBox(_T("Yes!"));
        break;
    case IDOK:
        // Do something 
        break;
    case IDCANCEL:
        AfxMessageBox(_T("IDCANCEL!"));
        break;
    default:
        // Do something 
        break;
    }
}

标签: c++multithreadingmfc

解决方案


应用程序定义的消息从下载线程发布到您的主 GUI 线程,如下所示:

BOOL CProgressBarTest::OnInitDialog()
{
    CDialog::OnInitDialog();

    auto funDownload = []( HWND hwnd ){
        auto const downloadRetValue = ::SomeDownloadAPI(funDownloadCallback);

        ::PostMessage( hwnd, WM_APP_DOWNLOAD_FINISHED, static_cast<WPARAM>( downloadRetValue ), 0 );
    };

    m_thDownload = std::thread(funDownload, GetSafeHwnd());

    return TRUE;
}

注意 1:OnInitDialog()用来启动线程是因为这DoDataExchange()是一个非常糟糕的选择,因为它会被多次调用。OnInitDialog()将被调用一次。

注 2:无捕获 lambda 用于更好地将下载线程与 GUI 线程分离。从 GUI 对话框传递this到工作线程是灾难的根源,因为它很容易只写入 GUI 线程变量,而忽略所需的同步。除此之外,更少的耦合带来更少的依赖,这总是一件好事。

是什么WM_APP_DOWNLOAD_FINISHED?这是我的应用程序定义的消息 ID,我通常这样定义:

enum {
    WM_APP_0 = WM_APP,
    WM_APP_DOWNLOAD_FINISHED
    // for future extension...
};

添加消息映射条目:

BEGIN_MESSAGE_MAP(CProgressBarTest, CDialog)
    ON_MESSAGE( WM_APP_DOWNLOAD_FINISHED, &CProgressBarTest::OnDownloadFinished )
END_MESSAGE_MAP()

并定义消息处理程序以从取决于下载结果的对话框中返回一个值:

LRESULT CProgressBarTest::OnDownloadFinished( WPARAM wp, LPARAM lp ) 
{    
    m_thDownload.join();

    auto const downloadRetValue = wp;
    EndDialog( downloadRetValue == ERROR_SUCCESS ? IDYES : IDCANCEL );

    return 0; 
}

确保join()像我上面所做的那样使用线程以避免std::thread析构函数中的崩溃,这需要线程已被连接。


推荐阅读