如何在另一个线程中关闭 MFC 模态对话框并获得对话框返回值?

Posted

技术标签:

【中文标题】如何在另一个线程中关闭 MFC 模态对话框并获得对话框返回值?【英文标题】:How do I close a MFC Modal Dialog in another thread and can get the dialog return value? 【发布时间】:2019-06-19 08:42:02 【问题描述】:

我有一个 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;
    

【问题讨论】:

您以这种方式创建的两个对话框都属于主 (UI) 线程。 EndDialog() 必须从“对话过程”中调用,这也在主线程中运行。所以问题似乎是你从下载线程调用EndDialog()。一个简单的解决方法(并且 imo 技术上正确)可能是在下载完成或失败时从下载线程向对话框窗口发布自定义消息(例如 WM_APP + nn)。响应此消息,您可以使用所需的返回值调用 EndDialog() 【参考方案1】:

将application-defined message 从下载线程发布到您的主 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 线程分离。将this 从 GUI 对话框传递到工作线程会导致灾难,因为它很容易只写入 GUI 线程变量,而忽略所需的同步。除此之外,更少的耦合带来更少的依赖,这总是一件好事。

WM_APP_DOWNLOAD_FINISHED 是什么?这是我的application-defined message 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 析构函数崩溃,这需要线程已被加入。

【讨论】:

【参考方案2】:

也许这是一种解决方法。

Can I return a custom value from a dialog box's DoModal function?

不要使用 DoModal 的返回值。只需为CProgressBarTest 添加一个成员函数。

【讨论】:

以上是关于如何在另一个线程中关闭 MFC 模态对话框并获得对话框返回值?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 ParentViewController 中关闭 formSheet 模态?

如何在 SwiftUI 中关闭 ResearchKit 模态视图?

如何在本机反应中关闭模态

打开“模态”窗口并在基于选项卡组的应用程序中关闭

如何创建一个模态的对话框

如何从自己的子视图控制器中关闭模态 uitabbarcontroller