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

时间:2019-06-19 08:42:02

标签: c++ multithreading 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;
    }
}

2 个答案:

答案 0 :(得分:1)

像这样从下载线程向您的主GUI线程发布application-defined message

Sub addSchoolRunToCalendar()
    Dim datStart As Date
    Dim datEnd As Date
    Dim oView As Outlook.View
    Dim oCalView As Outlook.CalendarView
    Dim oExpl As Outlook.Explorer
    Dim oFolder As Outlook.folder
    Dim oAppt As Outlook.AppointmentItem
    Const datNull As Date = #1/1/4501#

    Set oExpl = Application.ActiveExplorer
    Set oFolder = Application.ActiveExplorer.CurrentFolder
    Set oView = oExpl.CurrentView

    ' Check whether the active explorer is displaying a calendar view.
    If oView.ViewType = olCalendarView Then
        Set oCalView = oExpl.CurrentView

        datStart = oCalView.SelectedStartTime
        datStart = Format(datStart, "dd/mm/yyyy") & " " & Format("07:00", "hh:mm")

        Set oAppt = oFolder.Items.Add("IPM.Appointment")
        If datStart <> datNull And datEnd <> datNull Then
            oAppt.Subject = "School Run"
            oAppt.Location = "Home"
            oAppt.BusyStatus = olOutOfOffice
            oAppt.Start = datStart
            oAppt.Duration = "150"
            oAppt.ReminderSet = False
        End If

        oAppt.Save

    End If

End Sub

注意1:我正在使用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; } 启动线程,因为OnInitDialog()是一个非常糟糕的选择,因为它将被多次调用。 DoDataExchange()将只被调用一次。

注意2::无捕获lambda用于更好地将下载线程与GUI线程解耦。将OnInitDialog()从GUI对话框传递到工作线程是灾难的秘诀,因为它很容易只写GUI线程变量,而忽略了必需的同步。除此之外,更少的耦合和更少的依赖关系总是一件好事。

什么是this?这是我的application-defined message ID,通常这样定义:

WM_APP_DOWNLOAD_FINISHED

添加消息映射条目:

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()析构函数中的崩溃,这要求线程已经加入。

答案 1 :(得分:0)

也许这是一种解决方法。

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

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