From 0f1590a90ba15eb0f7094b2bbac4a52884c450ee Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 28 Oct 2017 19:20:28 +0200 Subject: [PATCH] Create MSW task dialog progress window with non-null parent This ensures the correct relationship between wxProgressDialog and its parent window, fixing different problems with Z-order and the progress dialog icon, but requires attaching the progress dialog thread input to the main thread, which creates its own problems: in particular, the task dialog is now only updated when the messages are dispatched in the main thread, i.e. more sluggishly than before. It also requires taking care to avoid the deadlocks as the task dialog thread now waits for the main thread to dispatch its messages too, as the child dialog sends messages to the parent window. In particular, we can't simply wait for the task dialog thread to terminate, but must do it while dispatching the events as well. Closes #13185. --- src/msw/progdlg.cpp | 102 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 12 deletions(-) diff --git a/src/msw/progdlg.cpp b/src/msw/progdlg.cpp index f3e812ca82..f660421f8a 100644 --- a/src/msw/progdlg.cpp +++ b/src/msw/progdlg.cpp @@ -366,6 +366,7 @@ wxProgressDialog::wxProgressDialog( const wxString& title, SetPDStyle(style); SetMaximum(maximum); + EnsureActiveEventLoopExists(); Show(); DisableOtherWindows(); @@ -388,14 +389,49 @@ wxProgressDialog::~wxProgressDialog() m_sharedData->m_notifications |= wxSPDD_DESTROYED; } - m_taskDialogRunner->Wait(); + // We can't use simple wxThread::Wait() here as we would deadlock because + // the task dialog thread expects this thread to process some messages + // (presumably those the task dialog sends to its parent during its + // destruction). + const WXHANDLE hThread = m_taskDialogRunner->MSWGetHandle(); + for ( bool cont = true; cont; ) + { + DWORD rc = ::MsgWaitForMultipleObjects + ( + 1, // number of objects to wait for + (HANDLE *)&hThread, // the objects + false, // wait for any objects, not all + INFINITE, // no timeout + QS_ALLINPUT | // return as soon as there are any events + QS_ALLPOSTMESSAGE + ); - delete m_taskDialogRunner; + switch ( rc ) + { + case 0xFFFFFFFF: + // This is unexpected, but we can't do anything about it and + // probably shouldn't continue waiting as we risk doing it + // forever. + wxLogLastError("MsgWaitForMultipleObjectsEx"); + cont = false; + break; + case WAIT_OBJECT_0: + // Thread has terminated. + cont = false; + break; + + default: + // An event has arrive, so dispatch it. + wxEventLoop::GetActive()->Dispatch(); + } + } + + // Enable the windows before deleting the task dialog to ensure that we + // can regain the activation. ReenableOtherWindows(); - if ( GetTopParent() ) - GetTopParent()->Raise(); + delete m_taskDialogRunner; #endif // wxHAS_MSW_TASKDIALOG } @@ -513,14 +549,12 @@ void wxProgressDialog::DispatchEvents() #ifdef wxHAS_MSW_TASKDIALOG // No need for HasNativeTaskDialog() check, we're only called when this is // the case. - wxEventLoopBase* const loop = wxEventLoop::GetActive(); - if ( loop ) - { - // We don't need to dispatch the user input events as the task dialog - // handles its own ones in its thread and we shouldn't react to any - // other user actions while the dialog is shown. - loop->YieldFor(wxEVT_CATEGORY_ALL & ~wxEVT_CATEGORY_USER_INPUT); - } + + // We don't need to dispatch the user input events as the task dialog + // handles its own ones in its thread and we shouldn't react to any + // other user actions while the dialog is shown. + wxEventLoop::GetActive()-> + YieldFor(wxEVT_CATEGORY_ALL & ~wxEVT_CATEGORY_USER_INPUT); #else // !wxHAS_MSW_TASKDIALOG wxFAIL_MSG( "unreachable" ); #endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG @@ -861,6 +895,16 @@ bool wxProgressDialog::Show(bool show) return false; } + // Wait until the dialog is shown as the program may need some time + // before it calls Update() and we want to show something to the user + // in the meanwhile. + while ( wxEventLoop::GetActive()->Dispatch() ) + { + wxCriticalSectionLocker locker(m_sharedData->m_cs); + if ( m_sharedData->m_hwnd ) + break; + } + // Do not show the underlying dialog. return false; } @@ -936,12 +980,42 @@ void wxProgressDialog::UpdateExpandedInformation(int value) void* wxProgressDialogTaskRunner::Entry() { + // If we have a parent, we must use it to have correct Z-order and icon, + // but this can only be done if we attach this thread input to the thread + // that created the parent window, i.e. the main thread. + wxWindow* parent = NULL; + { + wxCriticalSectionLocker locker(m_sharedData.m_cs); + parent = m_sharedData.m_parent; + } + + if ( parent ) + { + // Force the creation of the message queue for this thread. + MSG msg; + ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + // Attach its message queue to the main thread in order to allow using + // the parent window created by the main thread as task dialog parent. + if ( !::AttachThreadInput(::GetCurrentThreadId(), + wxThread::GetMainId(), + TRUE) ) + { + wxLogLastError(wxT("AttachThreadInput")); + + // Don't even try using the parent window from another thread, this + // won't work. + parent = NULL; + } + } + WinStruct tdc; wxMSWTaskDialogConfig wxTdc; { wxCriticalSectionLocker locker(m_sharedData.m_cs); + wxTdc.parent = parent; wxTdc.caption = m_sharedData.m_title.wx_str(); wxTdc.message = m_sharedData.m_message.wx_str(); @@ -1004,6 +1078,10 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc // Store the HWND for the main thread use. sharedData->m_hwnd = hwnd; + // The main thread is sitting in an event dispatching loop waiting + // for this dialog to be shown, so make sure it does get an event. + wxWakeUpIdle(); + // Set the maximum value and disable Close button. ::SendMessage( hwnd, TDM_SET_PROGRESS_BAR_RANGE,