Search code examples
windowsmultithreadingvisual-c++atl

Calling ShowWindow(SW_SHOW) on ATL modeless dialog not bringing window to front


I have a modeless dialog which I am showing and hiding programmatically from a worker thread. The problem is that the CWindow::ShowWindow(SW_SHOW) function is sometimes bringing the dialog to the front, and sometimes not.

Task: Show/Hide a simple logging & control window for an ATL COM object (dll) that runs in a separate thread. The COM object isn't a control: it is being used to wrap processing code for use in Excel VBA.

I've created a CMyDialog class derived from the CAxDialogImpl template. Then I'm using the CWorkerThread template for the worker thread. This is the Execute block (ie the function that runs in the worker thread).

HRESULT CLogProcessor::Execute(DWORD_PTR dwParam, HANDLE hObject)
{
    m_bRunning = true; //Winging it as this isn't thread-safe, but I don't think that's the issue.
    if (m_pDlg == 0)
    {
        m_pDlg = new CMyDialog(); //My dialog class
        m_pDlg->SetStopEvent(m_hStopEvent); //Save the handle of the stop event in the Dialog class
        m_pDlg->Create(NULL); //Create with no parent
        m_pDlg->ShowWindow(SW_SHOW); //Show the window
    }

    //Loop here until told to stop by the Stop Event being signalled in the calling thread
    while (WaitForSingleObject(m_hStopEvent, 0) == WAIT_TIMEOUT) //Polling Stop Event
    {
        //Have to keep the message loop going!
        MSG uMsg;
        while (PeekMessage(&uMsg, m_pDlg->m_hWnd, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&uMsg);
            DispatchMessage(&uMsg);
        }
        
        //The thread uses a protected "stack" of string messages to write to my logging window
        string strMsg;
        while (m_MsgStack.PopFront(strMsg))
        {
            std::wstring ws = std::wstring(strMsg.begin(), strMsg.end());
            CWindow eb(m_pDlg->GetDlgItem(IDC_EDIT1));
            eb.SendMessage(EM_REPLACESEL, 0, (LPARAM)ws.c_str());
        }
    }

    //Stop event has been signalled so clear up the dialog
    m_pDlg->DestroyWindow();
    delete m_pDlg;
    m_pDlg = 0;

    m_bRunning = false;

    return S_OK;
}

My Log object has two functions, StartLog() and StopLog() which are called by the COM object which is doing the main work of data processing.

void CLogProcessor::StartLog()
{
    if (!m_bRunning)
    {
        ::SetEvent(m_hStartEvent);
    }
}

void CLogProcessor::StopLog()
{
    if (m_bRunning)
    {
        ::SetEvent(m_hStopEvent);
    }
}

This works fine. I run VBA code in an Excel spreadsheet with buttons that call (via the COM object) StartLog() and StopLog(). The first time the Log Dialog appears all is good: it is activated and comes to the front. Firing StopLog() closes the dialog and it disappears. But the second time I call StartLog() the dialog window appears but it is not at the front, but behind other windows. I have tried a CWindow::BringWindowToTop() and/or SetFocus() call straight after the ShowWindow() but it makes no difference.

Another interesting feature is that my dialog has a system menu and so an 'X' to close it in the top-right hand corner. In the Dialog's message map, I am handling the WM_CLOSE message:

    BEGIN_MSG_MAP(CMyDialog)
        ... Other entries
        MESSAGE_HANDLER(WM_CLOSE, OnClose)
        ...
       CHAIN_MSG_MAP(CAxDialogImpl<CMyDialog>)
    END_MSG_MAP()

With OnClose() setting the Stop Event handle (previously passed to the Dialog object). Ie doing pretty much the same as the StopLog() function.

LRESULT CMyDialog::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    ::SetEvent(m_hStopEvent);
    
    return 0;
}

If I close the log dialog with the mouse click, it goes away as before. BUT the next time I call StartLog() the dialog does what it is supposed to do and comes to the front, which is the action I want. So I replaced the StopLog() code with a SendMessage(WM_CLOSE) to my dialog: it closed the window, but again it did not reappear on top. Another difference is that using the Close X on the dialog means that the Dialog has the focus when it is closed. Closing it from the Excel button means that the Excel window has the focus.

I realize this is rather a long question, and thanks very much if you have soldiered down to the end! I'd been keen for any solution / insight.


Solution

  • Update: I didn't solve the problem as stated, just re-worked the code to use a modal Dialog box in the worker thread. When I am done with the dialog, I PostMessage(WM_CLOSE) from the main thread: the dialog ends and the worker thread finishes (having set an event to say it is done, so that the thread is not destroyed before the Dialog is cleaned up).

    The dialog polls the stack of strings to display in response to a WM_USER+xxx message, posted from the main thread using PostMessage();

    I still have the same issue that the dialog doesnt appear on top the second time I run the worker thread, but this time adding a ShowWindow(SW_SHOW) in the OnInitDialog() handler seems to bring the dialog to the top when it appears.

    It also means I don't need my own message loop.