Open a MFC dialog in a std::thread

2.9k views Asked by At

I would like to show a dialog to inform the user that the application is busy. To avoid blocking of the main thread, I was thinking to use a std::thread to show the dialog. Consider the following code:

InProcDlg inProcess;
std::thread t([ &inProcess ] {      
    inProcess.DoModal();
    delete inProcess;
});
// wait till process has finished 
::PostMessage(inProcess.m_hWnd, WM_USER + 1, 0, 0);
if (t.joinable()){
    t.join();
}

InProcDlg.cpp

BEGIN_MESSAGE_MAP(InProcDlg, CDialogEx)
  ...
  ON_MESSAGE(WM_USER + 1, &InProcDlg::close)
END_MESSAGE_MAP()

LRESULT InProcDlg::close(WPARAM wParam, LPARAM lParam)
{
  UNREFERENCED_PARAMETER(wParam, lParam);
  EndDialog(1);
  return 0;
}

Running this code the dialog is shown properly. The dialog is also closed, but the main dialog is not shown, the application hangs in CreateRunDlgIndirect(). Trying to step in, while setting some breakpoints the main dialog is shown properly back again. Very strange. I would be very happy for any advices where I have to dive deeper in.

In the next step I would also like to show the process to the user, by sending an integer indicating the current state of process.

int *percent;
::PostMessage(inProcess.m_hWnd, WM_USER + 2, 0, reinterpret_cast<LPARAM>(percent));

How I can gain evidence that the dialog is already existing, before sending or posting a message? I'm using Visual Studio 2013.

1

There are 1 answers

1
RonTLV On

I can think of two ways to do that:

Modeless dialog

https://www.codeproject.com/Articles/1651/Tutorial-Modeless-Dialogs-with-MFC

User thread (UI thread)

Creating a brother to the main UI thread (CWinApp) by using CWinThread. Most important is to assign CWinThread::m_pMainWnd member, with a pointer to a Dialog. If the dialog is Modal you return FALSE right after the call to DoModal, and return TRUE for Modeless.

class CMainFrame : public CFrameWnd {
    // pointer to thread
    CWinThread* m_pUserThread;
}

start thread

m_pUserThread = AfxBeginThread(RUNTIME_CLASS(CUserThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
m_pUserThread->m_bAutoDelete = TRUE;
m_pUserThread->ResumeThread();

headr file**

class CUserThread : public CWinThread
{
    DECLARE_DYNCREATE(CUserThread)
public:
    CUserThread();

    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CUserThread)
    public:
    virtual BOOL InitInstance();
    virtual int ExitInstance();
    //}}AFX_VIRTUAL

protected:
    virtual ~CUserThread();
    // Generated message map functions
    //{{AFX_MSG(CUserThread)
        // NOTE - the ClassWizard will add and remove member functions here.
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
private:
    CUserMsg* m_pDlg;
}

source file

#define DO_MODAL
BOOL CUserThread::InitInstance()
{

    #ifdef DO_MODAL
    // create and assign Modeless dialog
    CUserMsg dlg;
    m_pMainWnd = &m_pDlg;
    dlg.DoModal();
    return FALSE;
    #endif

    #ifdnef DO_MODAL
    // create and assign Modal dialog
    m_pDlg = new CUserMsg();
    m_pDlg->Create( IDD_USER_DLG, AfxGetMainWnd() );
    m_pMainWnd = m_pDlg;
    return TRUE;
    #endif
}

int CUserThread::ExitInstance()
{
    // check for null
    m_pDlg->SendMessage( WM_CLOSE ) ;
    delete m_pDlg;

    return CWinThread::ExitInstance();
}

to terminate the thread

// post quit message to thread
m_pUserThread->PostThreadMessage(WM_QUIT,0,0);

// wait until thread termineates
::WaitForSingleObject(m_pUserThread->m_hThread,INFINITE);

For both ways I would highly recommend making the dialog as a top most window:

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

    // TODO: Add extra initialization here
    SetWindowPos( &wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | WS_EX_TOPMOST );
    return TRUE;
}