Merge branch 'progress-dialog-fixes'

Make native MSW wxProgressDialog much more usable and some minor
improvements to the generic version.
This commit is contained in:
Vadim Zeitlin
2017-11-17 17:58:03 +01:00
10 changed files with 545 additions and 196 deletions

View File

@@ -223,6 +223,7 @@ wxMSW:
- Fix updating radio groups when non-radio item is inserted to wxMenu.
- Fix autoselecting the contents of wxTextCtrl with wxWANTS_CHARS style.
- Implement SetIcon(), SetPosition(), GetPosition() for native wxProgressDialog.
- Fix focus-related problems when using native wxProgressDialog.
- Fix crash when reparenting the currently focused window to another TLW.
wxOSX:

View File

@@ -12,6 +12,7 @@
#define __PROGDLGH_G__
#include "wx/dialog.h"
#include "wx/weakref.h"
class WXDLLIMPEXP_FWD_CORE wxButton;
class WXDLLIMPEXP_FWD_CORE wxEventLoop;
@@ -43,18 +44,18 @@ public:
virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL);
virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL);
void Resume();
virtual void Resume();
int GetValue() const;
int GetRange() const;
wxString GetMessage() const;
virtual int GetValue() const;
virtual int GetRange() const;
virtual wxString GetMessage() const;
void SetRange(int maximum);
virtual void SetRange(int maximum);
// Return whether "Cancel" or "Skip" button was pressed, always return
// false if the corresponding button is not shown.
bool WasCancelled() const;
bool WasSkipped() const;
virtual bool WasCancelled() const;
virtual bool WasSkipped() const;
// Must provide overload to avoid hiding it (and warnings about it)
virtual void Update() wxOVERRIDE { wxDialog::Update(); }
@@ -104,6 +105,9 @@ protected:
// Converts seconds to HH:mm:ss format.
static wxString GetFormattedTime(unsigned long timeInSec);
// Create a new event loop if there is no currently running one.
void EnsureActiveEventLoopExists();
// callback for optional abort button
void OnCancel(wxCommandEvent&);
@@ -120,8 +124,8 @@ protected:
// the dialog was shown
void ReenableOtherWindows();
// Set the top level parent we store from the parent window provided when
// creating the dialog.
// Store the parent window as wxWindow::m_parent and also set the top level
// parent reference we store in this class itself.
void SetTopParent(wxWindow* parent);
// return the top level parent window of this dialog (may be NULL)
@@ -183,8 +187,9 @@ private:
*m_estimated,
*m_remaining;
// parent top level window (may be NULL)
wxWindow *m_parentTop;
// Reference to the parent top level window, automatically becomes NULL if
// it it is destroyed and could be always NULL if it's not given at all.
wxWindowRef m_parentTop;
// Progress dialog styles: this is not the same as m_windowStyle because
// wxPD_XXX constants clash with the existing TLW styles so to be sure we

View File

@@ -26,17 +26,17 @@ public:
virtual bool Update(int value, const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE;
virtual bool Pulse(const wxString& newmsg = wxEmptyString, bool *skip = NULL) wxOVERRIDE;
void Resume();
virtual void Resume() wxOVERRIDE;
int GetValue() const;
wxString GetMessage() const;
virtual int GetValue() const wxOVERRIDE;
virtual wxString GetMessage() const wxOVERRIDE;
void SetRange(int maximum);
virtual void SetRange(int maximum) wxOVERRIDE;
// Return whether "Cancel" or "Skip" button was pressed, always return
// false if the corresponding button is not shown.
bool WasSkipped() const;
bool WasCancelled() const;
virtual bool WasSkipped() const wxOVERRIDE;
virtual bool WasCancelled() const wxOVERRIDE;
virtual void SetTitle(const wxString& title) wxOVERRIDE;
virtual wxString GetTitle() const wxOVERRIDE;
@@ -44,6 +44,8 @@ public:
virtual void SetIcons(const wxIconBundle& icons) wxOVERRIDE;
virtual void DoMoveWindow(int x, int y, int width, int height) wxOVERRIDE;
virtual void DoGetPosition(int *x, int *y) const wxOVERRIDE;
virtual void DoGetSize(int *width, int *height) const wxOVERRIDE;
virtual void Fit() wxOVERRIDE;
virtual bool Show( bool show = true ) wxOVERRIDE;
@@ -53,15 +55,24 @@ public:
virtual WXWidget GetHandle() const wxOVERRIDE;
private:
// Performs common routines to Update() and Pulse(). Requires the
// shared object to have been entered.
// Common part of Update() and Pulse().
//
// Returns false if the user requested cancelling the dialog.
bool DoNativeBeforeUpdate(bool *skip);
// Dispatch the pending events to let the windows to update, just as the
// generic version does. This is done as part of DoNativeBeforeUpdate().
void DispatchEvents();
// Updates the various timing informations for both determinate
// and indeterminate modes. Requires the shared object to have
// been entered.
void UpdateExpandedInformation(int value);
// Get the task dialog geometry when using the native dialog.
wxRect GetTaskDialogRect() const;
wxProgressDialogTaskRunner *m_taskDialogRunner;
wxProgressDialogSharedData *m_sharedData;

View File

@@ -90,6 +90,9 @@ public:
// NULL if getting the system menu failed.
wxMenu *MSWGetSystemMenu() const;
// Enable or disable the close button of the specified window.
static bool MSWEnableCloseButton(WXHWND hwnd, bool enable = true);
// implementation from now on
// --------------------------

View File

@@ -33,6 +33,26 @@
wxProgressDialog in a multi-threaded application you should be sure to use
wxThreadEvent for your inter-threads communications).
Although wxProgressDialog is not really modal, it should be created on the
stack, and not the heap, as other modal dialogs, e.g. use it like this:
@code
void MyFrame::SomeFunc()
{
wxProgressDialog dialog(...);
for ( int i = 0; i < 100; ++i ) {
if ( !dialog.Update(i)) {
// Cancelled by user.
break;
}
... do something time-consuming (but not too much) ...
}
}
@endcode
Note that this becomes even more important if the dialog is instantiated
during the program initialization, e.g. from wxApp::OnInit(): the dialog
must be destroyed before the main event loop is started in this case.
@beginStyleTable
@style{wxPD_APP_MODAL}
Make the progress dialog modal. If this flag is not given, it is
@@ -192,9 +212,19 @@ public:
for the user to dismiss it, meaning that this function does not return
until this happens.
Notice that you may want to call Fit() to change the dialog size to
conform to the length of the new message if desired. The dialog does
not do this automatically.
Notice that if @a newmsg is longer than the currently shown message,
the dialog will be automatically made wider to account for it. However
if the new message is shorter than the previous one, the dialog doesn't
shrink back to avoid constant resizes if the message is changed often.
To do this and fit the dialog to its current contents you may call
Fit() explicitly. However the native MSW implementation of this class
does make the dialog shorter if the new text has fewer lines of text
than the old one, so it is recommended to keep the number of lines of
text constant in order to avoid jarring dialog size changes. You may
also want to make the initial message, specified when creating the
dialog, wide enough to avoid having to resize the dialog later, e.g. by
appending a long string of unbreakable spaces (@c wxString(L'\u00a0',
100)) to it.
@param value
The new value of the progress meter. It should be less than or equal to

View File

@@ -233,6 +233,9 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
#if wxUSE_PROGRESSDLG
EVT_MENU(DIALOGS_PROGRESS, MyFrame::ShowProgress)
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
EVT_MENU(DIALOGS_PROGRESS_GENERIC, MyFrame::ShowProgressGeneric)
#endif // wxHAS_NATIVE_PROGRESSDIALOG
#endif // wxUSE_PROGRESSDLG
EVT_MENU(DIALOGS_APP_PROGRESS, MyFrame::ShowAppProgress)
@@ -349,7 +352,25 @@ bool MyApp::OnInit()
);
for ( int i = 0; i <= PROGRESS_COUNT; i++ )
{
if ( !dlg.Update(i) )
wxString msg;
switch ( i )
{
case 15:
msg = "And the same dialog but with a very, very, very long"
" message, just to test how it appears in this case.";
break;
case 30:
msg = "Back to brevity";
break;
case 80:
msg = "Back and adjusted";
dlg.Fit();
break;
}
if ( !dlg.Update(i, msg) )
break;
wxMilliSleep(50);
@@ -490,6 +511,10 @@ bool MyApp::OnInit()
#if wxUSE_PROGRESSDLG
info_menu->Append(DIALOGS_PROGRESS, wxT("Pro&gress dialog\tCtrl-G"));
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
info_menu->Append(DIALOGS_PROGRESS_GENERIC,
wxT("Generic progress dialog\tCtrl-Alt-G"));
#endif // wxHAS_NATIVE_PROGRESSDIALOG
#endif // wxUSE_PROGRESSDLG
info_menu->Append(DIALOGS_APP_PROGRESS, wxT("&App progress\tShift-Ctrl-G"));
@@ -2652,10 +2677,10 @@ void MyFrame::OnExit(wxCommandEvent& WXUNUSED(event) )
#if wxUSE_PROGRESSDLG
static const int max = 100;
void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
{
static const int max = 100;
wxProgressDialog dialog("Progress dialog example",
// "Reserve" enough space for the multiline
// messages below, we'll change it anyhow
@@ -2673,6 +2698,30 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
wxPD_SMOOTH // - makes indeterminate mode bar on WinXP very small
);
DoShowProgress(dialog);
}
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
void MyFrame::ShowProgressGeneric( wxCommandEvent& WXUNUSED(event) )
{
wxGenericProgressDialog dialog("Generic progress dialog example",
wxString(' ', 100) + "\n\n\n\n",
max,
this,
wxPD_CAN_ABORT |
wxPD_CAN_SKIP |
wxPD_APP_MODAL |
wxPD_ELAPSED_TIME |
wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME |
wxPD_SMOOTH);
DoShowProgress(dialog);
}
#endif // wxHAS_NATIVE_PROGRESSDIALOG
void MyFrame::DoShowProgress(wxGenericProgressDialog& dialog)
{
bool cont = true;
for ( int i = 0; i <= max; i++ )
{
@@ -2721,8 +2770,8 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
{
i += max/4;
if ( i >= 100 )
i = 99;
if ( i >= max )
i = max - 1;
}
if ( !cont )
@@ -2736,7 +2785,7 @@ void MyFrame::ShowProgress( wxCommandEvent& WXUNUSED(event) )
dialog.Resume();
}
wxMilliSleep(200);
wxMilliSleep(100);
}
if ( !cont )
@@ -2768,7 +2817,7 @@ void MyFrame::ShowAppProgress( wxCommandEvent& WXUNUSED(event) )
{
progress.SetValue(i);
wxMilliSleep(500);
wxMilliSleep(200);
}
wxLogStatus("Progress finished");

View File

@@ -452,6 +452,10 @@ public:
#if wxUSE_PROGRESSDLG
void ShowProgress(wxCommandEvent& event);
#ifdef wxHAS_NATIVE_PROGRESSDIALOG
void ShowProgressGeneric(wxCommandEvent& event);
#endif // wxHAS_NATIVE_PROGRESSDIALOG
void DoShowProgress(wxGenericProgressDialog& dialog);
#endif // wxUSE_PROGRESSDLG
void ShowAppProgress(wxCommandEvent& event);
@@ -596,6 +600,7 @@ enum
DIALOGS_ONTOP,
DIALOGS_MODELESS_BTN,
DIALOGS_PROGRESS,
DIALOGS_PROGRESS_GENERIC,
DIALOGS_APP_PROGRESS,
DIALOGS_ABOUTDLG_SIMPLE,
DIALOGS_ABOUTDLG_FANCY,

View File

@@ -133,6 +133,7 @@ wxGenericProgressDialog::wxGenericProgressDialog(const wxString& title,
void wxGenericProgressDialog::SetTopParent(wxWindow* parent)
{
m_parent = parent;
m_parentTop = GetParentForModalDialog(parent, GetWindowStyle());
}
@@ -144,13 +145,9 @@ bool wxGenericProgressDialog::Create( const wxString& title,
{
SetTopParent(parent);
m_parentTop = wxGetTopLevelParent(parent);
m_pdStyle = style;
wxWindow* const
realParent = GetParentForModalDialog(parent, GetWindowStyle());
if (!wxDialog::Create(realParent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, GetWindowStyle()))
if (!wxDialog::Create(m_parentTop, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, GetWindowStyle()))
return false;
SetMaximum(maximum);
@@ -160,11 +157,7 @@ bool wxGenericProgressDialog::Create( const wxString& title,
// even if this means we have to start it ourselves (this happens most
// commonly during the program initialization, e.g. for the progress
// dialogs shown from overridden wxApp::OnInit()).
if ( !wxEventLoopBase::GetActive() )
{
m_tempEventLoop = new wxEventLoop;
wxEventLoop::SetActive(m_tempEventLoop);
}
EnsureActiveEventLoopExists();
#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__)
// we have to remove the "Close" button from the title bar then as it is
@@ -363,6 +356,15 @@ wxString wxGenericProgressDialog::GetFormattedTime(unsigned long timeInSec)
return timeAsHMS;
}
void wxGenericProgressDialog::EnsureActiveEventLoopExists()
{
if ( !wxEventLoopBase::GetActive() )
{
m_tempEventLoop = new wxEventLoop;
wxEventLoop::SetActive(m_tempEventLoop);
}
}
wxStaticText *
wxGenericProgressDialog::CreateLabel(const wxString& text, wxSizer *sizer)
{
@@ -695,6 +697,21 @@ wxGenericProgressDialog::~wxGenericProgressDialog()
if ( m_tempEventLoop )
{
// If another event loop has been installed as active during the life
// time of this object, we shouldn't deactivate it, but we also can't
// delete our m_tempEventLoop in this case because it risks leaving the
// new event loop with a dangling pointer, which it will set back as
// the active loop when it exits, resulting in a crash. So we have no
// choice but to just leak this pointer then, which is, of course, bad
// and usually easily avoidable by just destroying the progress dialog
// sooner, so warn the programmer about it.
wxCHECK_RET
(
wxEventLoopBase::GetActive() == m_tempEventLoop,
"current event loop must not be changed during "
"wxGenericProgressDialog lifetime"
);
wxEventLoopBase::SetActive(NULL);
delete m_tempEventLoop;
}
@@ -765,8 +782,17 @@ void wxGenericProgressDialog::UpdateMessage(const wxString &newmsg)
{
if ( !newmsg.empty() && newmsg != m_msg->GetLabel() )
{
const wxSize sizeOld = m_msg->GetSize();
m_msg->SetLabel(newmsg);
if ( m_msg->GetSize().x > sizeOld.x )
{
// Resize the dialog to fit its new, longer contents instead of
// just truncating it.
Fit();
}
// allow the window to repaint:
// NOTE: since we yield only for UI events with this call, there
// should be no side-effects

View File

@@ -57,7 +57,6 @@ const int wxSPDD_EXPINFO_CHANGED = 0x0020;
const int wxSPDD_ENABLE_SKIP = 0x0040;
const int wxSPDD_ENABLE_ABORT = 0x0080;
const int wxSPDD_DISABLE_SKIP = 0x0100;
const int wxSPDD_DISABLE_ABORT = 0x0200;
const int wxSPDD_FINISHED = 0x0400;
const int wxSPDD_DESTROYED = 0x0800;
const int wxSPDD_ICON_CHANGED = 0x1000;
@@ -81,6 +80,7 @@ public:
m_value = 0;
m_progressBarMarquee = false;
m_skipped = false;
m_msgChangeElementText = TDM_UPDATE_ELEMENT_TEXT;
m_notifications = 0;
m_parent = NULL;
}
@@ -105,9 +105,48 @@ public:
bool m_progressBarMarquee;
bool m_skipped;
// The task dialog message to use for changing the text of its elements:
// it is set to TDM_SET_ELEMENT_TEXT by Fit() to let the dialog adjust
// itself to the size of its elements during the next update, but otherwise
// TDM_UPDATE_ELEMENT_TEXT is used in order to prevent the dialog from
// performing a layout on each update, which is annoying as it can result
// in its size constantly changing.
int m_msgChangeElementText;
// Bit field that indicates fields that have been modified by the
// main thread so the task dialog runner knows what to update.
int m_notifications;
// Helper function to split a single message, passed via our public API,
// into the title and the main content body used by the native dialog.
//
// Note that it uses m_message and so must be called with m_cs locked.
void SplitMessageIntoTitleAndBody(wxString& title, wxString& body) const
{
title = m_message;
const size_t posNL = title.find('\n');
if ( posNL != wxString::npos )
{
// There can an extra new line between the first and subsequent
// lines to separate them as it looks better with the generic
// version -- but in this one, they're already separated by the use
// of different dialog elements, so suppress the extra new line.
int numNLs = 1;
if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
numNLs++;
body.assign(title, posNL + numNLs, wxString::npos);
title.erase(posNL);
}
else // A single line
{
// Don't use title without the body, this doesn't make sense.
body.clear();
title.swap(body);
}
}
};
// Runner thread that takes care of displaying and updating the
@@ -182,6 +221,16 @@ BOOL CALLBACK DisplayCloseButton(HWND hwnd, LPARAM lParam)
return TRUE;
}
// This function enables or disables both the cancel button in the task dialog
// and the close button in its title bar, as they perform the same function and
// so should be kept in the same state.
void EnableCloseButtons(HWND hwnd, bool enable)
{
::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, enable ? TRUE : FALSE);
wxTopLevelWindow::MSWEnableCloseButton(hwnd, enable);
}
void PerformNotificationUpdates(HWND hwnd,
wxProgressDialogSharedData *sharedData)
{
@@ -196,6 +245,33 @@ void PerformNotificationUpdates(HWND hwnd,
if ( sharedData->m_notifications & wxSPDD_VALUE_CHANGED )
{
// Use a hack to avoid progress bar animation: we can't afford to use
// it because animating the gauge movement smoothly requires a
// constantly running message loop and while it does run in this (task
// dialog) thread, it is often blocked from proceeding by some lock
// held by the main thread which is busy doing something and may not
// dispatch events frequently enough. So, in practice, the animation
// can lag far behind the real value and results in showing a wrong
// value in the progress bar.
//
// To prevent this from happening, set the progress bar value to
// something greater than its maximal value and then move it back: in
// the current implementations of the progress bar control, moving its
// position backwards does it directly, without using the animation,
// which is exactly what we want here.
//
// Finally notice that this hack doesn't really work for the last
// value, but while we could use a nested hack and temporarily increase
// the progress bar range when the value is equal to it, it isn't
// actually necessary in practice because when we reach the end of the
// bar the dialog is either hidden immediately anyhow or the main
// thread enters a modal event loop which does dispatch events and so
// it's not a problem to have an animated transition in this particular
// case.
::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_POS,
sharedData->m_value + 1,
0 );
::SendMessage( hwnd,
TDM_SET_PROGRESS_BAR_POS,
sharedData->m_value,
@@ -235,39 +311,31 @@ void PerformNotificationUpdates(HWND hwnd,
{
// Split the message in the title string and the rest if it has
// multiple lines.
wxString
title = sharedData->m_message,
body;
const size_t posNL = title.find('\n');
if ( posNL != wxString::npos )
{
// There can an extra new line between the first and subsequent
// lines to separate them as it looks better with the generic
// version -- but in this one, they're already separated by the use
// of different dialog elements, so suppress the extra new line.
int numNLs = 1;
if ( posNL < title.length() - 1 && title[posNL + 1] == '\n' )
numNLs++;
body.assign(title, posNL + numNLs, wxString::npos);
title.erase(posNL);
}
else // A single line
{
// Don't use title without the body, this doesn't make sense.
title.swap(body);
}
wxString title, body;
sharedData->SplitMessageIntoTitleAndBody(title, body);
::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT,
sharedData->m_msgChangeElementText,
TDE_MAIN_INSTRUCTION,
wxMSW_CONV_LPARAM(title) );
::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT,
sharedData->m_msgChangeElementText,
TDE_CONTENT,
wxMSW_CONV_LPARAM(body) );
// After using TDM_SET_ELEMENT_TEXT once, we don't want to use it for
// the subsequent updates as it could result in dialog size changing
// unexpectedly, so reset it (which does nothing if we had already done
// it, of course, but it's not a problem).
//
// Notice that, contrary to its documentation, even using this message
// still increases the dialog size if the new text is longer (at least
// under Windows 7), but it doesn't shrink back if the text becomes
// shorter later and stays at the bigger size which is still a big gain
// as it prevents jumping back and forth between the smaller and larger
// sizes.
sharedData->m_msgChangeElementText = TDM_UPDATE_ELEMENT_TEXT;
}
if ( sharedData->m_notifications & wxSPDD_EXPINFO_CHANGED )
@@ -276,8 +344,15 @@ void PerformNotificationUpdates(HWND hwnd,
sharedData->m_expandedInformation;
if ( !expandedInformation.empty() )
{
// Here we never need to use TDM_SET_ELEMENT_TEXT as the size of
// the expanded information doesn't change drastically.
//
// Notice that TDM_UPDATE_ELEMENT_TEXT for this element only works
// when using TDF_EXPAND_FOOTER_AREA, as we do. Without this flag,
// only TDM_SET_ELEMENT_TEXT could be used as otherwise the dialog
// layout becomes completely mangled (at least under Windows 7).
::SendMessage( hwnd,
TDM_SET_ELEMENT_TEXT,
TDM_UPDATE_ELEMENT_TEXT,
TDE_EXPANDED_INFORMATION,
wxMSW_CONV_LPARAM(expandedInformation) );
}
@@ -287,14 +362,11 @@ void PerformNotificationUpdates(HWND hwnd,
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, TRUE );
if ( sharedData->m_notifications & wxSPDD_ENABLE_ABORT )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
EnableCloseButtons(hwnd, true);
if ( sharedData->m_notifications & wxSPDD_DISABLE_SKIP )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
if ( sharedData->m_notifications & wxSPDD_DISABLE_ABORT )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
// Is the progress finished?
if ( sharedData->m_notifications & wxSPDD_FINISHED )
{
@@ -304,7 +376,7 @@ void PerformNotificationUpdates(HWND hwnd,
{
// Change Cancel into Close and activate the button.
::SendMessage( hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE );
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, TRUE );
EnableCloseButtons(hwnd, true);
::EnumChildWindows( hwnd, DisplayCloseButton,
(LPARAM) sharedData );
}
@@ -337,6 +409,7 @@ wxProgressDialog::wxProgressDialog( const wxString& title,
SetPDStyle(style);
SetMaximum(maximum);
EnsureActiveEventLoopExists();
Show();
DisableOtherWindows();
@@ -359,14 +432,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
}
@@ -375,21 +483,26 @@ bool wxProgressDialog::Update(int value, const wxString& newmsg, bool *skip)
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
if ( !DoNativeBeforeUpdate(skip) )
{
// Dialog was cancelled.
return false;
}
value /= m_factor;
wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Do nothing in canceled state.
if ( !DoNativeBeforeUpdate(skip) )
return false;
if ( value != m_sharedData->m_value )
{
m_sharedData->m_value = value;
m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
}
value /= m_factor;
wxASSERT_MSG( value <= m_maximum, wxT("invalid progress value") );
m_sharedData->m_value = value;
m_sharedData->m_notifications |= wxSPDD_VALUE_CHANGED;
if ( !newmsg.empty() )
if ( !newmsg.empty() && newmsg != m_message )
{
m_message = newmsg;
m_sharedData->m_message = newmsg;
@@ -445,11 +558,13 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Do nothing in canceled state.
if ( !DoNativeBeforeUpdate(skip) )
{
// Dialog was cancelled.
return false;
}
wxCriticalSectionLocker locker(m_sharedData->m_cs);
if ( !m_sharedData->m_progressBarMarquee )
{
@@ -457,15 +572,15 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
m_sharedData->m_notifications |= wxSPDD_PBMARQUEE_CHANGED;
}
if ( !newmsg.empty() )
if ( !newmsg.empty() && newmsg != m_message )
{
m_message = newmsg;
m_sharedData->m_message = newmsg;
m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
}
// The value passed here doesn't matter, only elapsed time makes sense
// in indeterminate mode anyhow.
// Value of 0 is special and is used when we can't estimate the
// remaining and total times, which is exactly what we need here.
UpdateExpandedInformation(0);
return m_sharedData->m_state != Canceled;
@@ -475,32 +590,49 @@ bool wxProgressDialog::Pulse(const wxString& newmsg, bool *skip)
return wxGenericProgressDialog::Pulse( newmsg, skip );
}
void wxProgressDialog::DispatchEvents()
{
#ifdef wxHAS_MSW_TASKDIALOG
// No need for HasNativeTaskDialog() check, we're only called when this is
// the case.
// 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
}
bool wxProgressDialog::DoNativeBeforeUpdate(bool *skip)
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
DispatchEvents();
wxCriticalSectionLocker locker(m_sharedData->m_cs);
if ( m_sharedData->m_skipped )
{
if ( m_sharedData->m_skipped )
if ( skip && !*skip )
{
if ( skip && !*skip )
{
*skip = true;
m_sharedData->m_skipped = false;
m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
}
*skip = true;
m_sharedData->m_skipped = false;
m_sharedData->m_notifications |= wxSPDD_ENABLE_SKIP;
}
if ( m_sharedData->m_state == Canceled )
m_timeStop = m_sharedData->m_timeStop;
return m_sharedData->m_state != Canceled;
}
#endif // wxHAS_MSW_TASKDIALOG
if ( m_sharedData->m_state == Canceled )
m_timeStop = m_sharedData->m_timeStop;
return m_sharedData->m_state != Canceled;
#else // !wxHAS_MSW_TASKDIALOG
wxUnusedVar(skip);
wxFAIL_MSG( "unreachable" );
return false;
#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
}
void wxProgressDialog::Resume()
@@ -514,7 +646,7 @@ void wxProgressDialog::Resume()
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state;
m_sharedData->m_state = Continue;
// "Skip" was disabled when "Cancel" had been clicked, so re-enable
// it now.
@@ -542,20 +674,16 @@ void wxProgressDialog::Resume()
#endif // wxHAS_MSW_TASKDIALOG
}
WXWidget wxProgressDialog::GetHandle() const
{
WXWidget wxProgressDialog::GetHandle() const
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
HWND hwnd;
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state;
hwnd = m_sharedData->m_hwnd;
}
return hwnd;
wxCriticalSectionLocker locker(m_sharedData->m_cs);
return m_sharedData->m_hwnd;
}
#endif
#endif // wxHAS_MSW_TASKDIALOG
return wxGenericProgressDialog::GetHandle();
}
@@ -715,21 +843,33 @@ void wxProgressDialog::DoMoveWindow(int x, int y, int width, int height)
wxGenericProgressDialog::DoMoveWindow(x, y, width, height);
}
wxRect wxProgressDialog::GetTaskDialogRect() const
{
wxRect r;
#ifdef wxHAS_MSW_TASKDIALOG
if ( m_sharedData )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
r = wxRectFromRECT(wxGetWindowRect(m_sharedData->m_hwnd));
}
#else // !wxHAS_MSW_TASKDIALOG
wxFAIL_MSG( "unreachable" );
#endif // wxHAS_MSW_TASKDIALOG/!wxHAS_MSW_TASKDIALOG
return r;
}
void wxProgressDialog::DoGetPosition(int *x, int *y) const
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
wxPoint pos;
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
m_sharedData->m_state = m_state;
pos = m_sharedData->m_winPosition;
}
const wxRect r = GetTaskDialogRect();
if (x)
*x = pos.x;
*x = r.x;
if (y)
*y = pos.y;
*y = r.y;
return;
}
@@ -738,6 +878,42 @@ void wxProgressDialog::DoGetPosition(int *x, int *y) const
wxGenericProgressDialog::DoGetPosition(x, y);
}
void wxProgressDialog::DoGetSize(int *width, int *height) const
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
const wxRect r = GetTaskDialogRect();
if ( width )
*width = r.width;
if ( height )
*height = r.height;
return;
}
#endif // wxHAS_MSW_TASKDIALOG
wxGenericProgressDialog::DoGetSize(width, height);
}
void wxProgressDialog::Fit()
{
#ifdef wxHAS_MSW_TASKDIALOG
if ( HasNativeTaskDialog() )
{
wxCriticalSectionLocker locker(m_sharedData->m_cs);
// Force the task dialog to use this message to adjust it layout.
m_sharedData->m_msgChangeElementText = TDM_SET_ELEMENT_TEXT;
// Don't change the message, but pretend that it did change.
m_sharedData->m_notifications |= wxSPDD_MESSAGE_CHANGED;
}
#endif // wxHAS_MSW_TASKDIALOG
wxGenericProgressDialog::Fit();
}
bool wxProgressDialog::Show(bool show)
{
#ifdef wxHAS_MSW_TASKDIALOG
@@ -777,8 +953,9 @@ bool wxProgressDialog::Show(bool show)
wxPD_ESTIMATED_TIME |
wxPD_REMAINING_TIME) )
{
// Use a non-empty string just to have the collapsible pane shown.
m_sharedData->m_expandedInformation = " ";
// Set the expanded information field from the beginning to avoid
// having to re-layout the dialog later when it changes.
UpdateExpandedInformation(0);
}
// Do launch the thread.
@@ -794,6 +971,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;
}
@@ -810,14 +997,15 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
unsigned long remainingTime;
UpdateTimeEstimates(value, elapsedTime, estimatedTime, remainingTime);
int realEstimatedTime = estimatedTime,
realRemainingTime = remainingTime;
if ( m_sharedData->m_progressBarMarquee )
// The value of 0 is special, we can't estimate anything before we have at
// least one update, so leave the times dependent on it indeterminate.
//
// This value is also used by Pulse(), as in the indeterminate mode we can
// never estimate anything.
if ( !value )
{
// In indeterminate mode we don't have any estimation neither for the
// remaining nor for estimated time.
realEstimatedTime =
realRemainingTime = -1;
estimatedTime =
remainingTime = static_cast<unsigned long>(-1);
}
wxString expandedInformation;
@@ -837,7 +1025,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
expandedInformation << GetEstimatedLabel()
<< " "
<< GetFormattedTime(realEstimatedTime);
<< GetFormattedTime(estimatedTime);
}
if ( HasPDFlag(wxPD_REMAINING_TIME) )
@@ -847,7 +1035,7 @@ void wxProgressDialog::UpdateExpandedInformation(int value)
expandedInformation << GetRemainingLabel()
<< " "
<< GetFormattedTime(realRemainingTime);
<< GetFormattedTime(remainingTime);
}
// Update with new timing information.
@@ -875,8 +1063,20 @@ void* wxProgressDialogTaskRunner::Entry()
{
wxCriticalSectionLocker locker(m_sharedData.m_cs);
// If we have a parent, we must use it to have correct Z-order and
// icon, even if this comes at the price of attaching this thread input
// to the thread that created the parent window, i.e. the main thread.
wxTdc.parent = m_sharedData.m_parent;
wxTdc.caption = m_sharedData.m_title.wx_str();
wxTdc.message = m_sharedData.m_message.wx_str();
// Split the message into the title and main body text in the same way
// as it's done later in PerformNotificationUpdates() when the message
// is changed by Update() or Pulse().
m_sharedData.SplitMessageIntoTitleAndBody
(
wxTdc.message,
wxTdc.extendedMessage
);
// MSWCommonTaskDialogInit() will add an IDCANCEL button but we need to
// give it the correct label.
@@ -887,10 +1087,6 @@ void* wxProgressDialogTaskRunner::Entry()
tdc.pfCallback = TaskDialogCallbackProc;
tdc.lpCallbackData = (LONG_PTR) &m_sharedData;
// Undo some of the effects of MSWCommonTaskDialogInit().
tdc.dwFlags &= ~TDF_EXPAND_FOOTER_AREA; // Expand in content area.
tdc.dwCommonButtons = 0; // Don't use common buttons.
if ( m_sharedData.m_style & wxPD_CAN_SKIP )
wxTdc.AddTaskDialogButton( tdc, Id_SkipBtn, 0, _("Skip") );
@@ -900,6 +1096,13 @@ void* wxProgressDialogTaskRunner::Entry()
{
tdc.pszExpandedInformation =
m_sharedData.m_expandedInformation.t_str();
// If we have elapsed/estimated/... times to show, show them from
// the beginning for consistency with the generic version and also
// because showing them later may be very sluggish if the main
// thread doesn't update the dialog sufficiently frequently, while
// hiding them still works reasonably well.
tdc.dwFlags |= TDF_EXPANDED_BY_DEFAULT;
}
}
@@ -930,6 +1133,10 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
LONG_PTR dwRefData
)
{
bool endDialog = false;
// Block for shared data critical section.
{
wxProgressDialogSharedData * const sharedData =
(wxProgressDialogSharedData *) dwRefData;
@@ -941,45 +1148,20 @@ 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,
0,
MAKELPARAM(0, sharedData->m_range) );
// We always create this task dialog with NULL parent because our
// parent in wx sense is a window created from a different thread
// and so can't be used as our real parent. However we still center
// this window on the parent one as the task dialogs do with their
// real parent usually.
if ( sharedData->m_parent )
{
wxRect rect(wxRectFromRECT(wxGetWindowRect(hwnd)));
rect = rect.CentreIn(sharedData->m_parent->GetRect());
::SetWindowPos(hwnd,
NULL,
rect.x,
rect.y,
-1,
-1,
SWP_NOACTIVATE |
SWP_NOOWNERZORDER |
SWP_NOSIZE |
SWP_NOZORDER);
}
// Store current position for the main thread use
// if no position update is pending.
if ( !(sharedData->m_notifications & wxSPDD_WINDOW_MOVED) )
{
RECT r = wxGetWindowRect(hwnd);
sharedData->m_winPosition = wxPoint(r.left, r.top);
}
// If we can't be aborted, the "Close" button will only be enabled
// when the progress ends (and not even then with wxPD_AUTO_HIDE).
if ( !(sharedData->m_style & wxPD_CAN_ABORT) )
::SendMessage( hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE );
EnableCloseButtons(hwnd, false);
break;
case TDN_BUTTON_CLICKED:
@@ -988,7 +1170,7 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
case Id_SkipBtn:
::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
sharedData->m_skipped = true;
return TRUE;
return S_FALSE;
case IDCANCEL:
if ( sharedData->m_state == wxProgressDialog::Finished )
@@ -998,7 +1180,7 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
sharedData->m_state = wxProgressDialog::Dismissed;
// Let Windows close the dialog.
return FALSE;
return S_OK;
}
// Close button on the window triggers an IDCANCEL press,
@@ -1006,26 +1188,48 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
// a finished dialog.
if ( sharedData->m_style & wxPD_CAN_ABORT )
{
wxCHECK_MSG
(
sharedData->m_state == wxProgressDialog::Continue,
TRUE,
"Dialog not in a cancelable state!"
);
switch ( sharedData->m_state )
{
case wxProgressDialog::Canceled:
// It can happen that we receive a second
// cancel request before we had time to process
// the first one, in which case simply do
// nothing for the subsequent one.
break;
::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
::SendMessage(hwnd, TDM_ENABLE_BUTTON, IDCANCEL, FALSE);
case wxProgressDialog::Continue:
::SendMessage(hwnd, TDM_ENABLE_BUTTON, Id_SkipBtn, FALSE);
EnableCloseButtons(hwnd, false);
sharedData->m_timeStop = wxGetCurrentTime();
sharedData->m_state = wxProgressDialog::Canceled;
sharedData->m_timeStop = wxGetCurrentTime();
sharedData->m_state = wxProgressDialog::Canceled;
break;
// States which shouldn't be possible here:
// We shouldn't have an (enabled) cancel button at
// all then.
case wxProgressDialog::Uncancelable:
// This one was already dealt with above.
case wxProgressDialog::Finished:
// Normally it shouldn't be possible to get any
// notifications after switching to this state.
case wxProgressDialog::Dismissed:
wxFAIL_MSG( "unreachable" );
break;
}
}
return TRUE;
return S_FALSE;
}
break;
case TDN_TIMER:
PerformNotificationUpdates(hwnd, sharedData);
// Don't perform updates if nothing needs to be done.
if ( sharedData->m_notifications )
PerformNotificationUpdates(hwnd, sharedData);
/*
Decide whether we should end the dialog. This is done if either
@@ -1041,21 +1245,30 @@ wxProgressDialogTaskRunner::TaskDialogCallbackProc
(sharedData->m_state == wxProgressDialog::Finished &&
sharedData->m_style & wxPD_AUTO_HIDE) )
{
::EndDialog( hwnd, IDCLOSE );
// Don't call EndDialog() from here as it could deadlock
// because we are inside the shared data critical section, do
// it below after leaving it instead.
endDialog = true;
}
sharedData->m_notifications = 0;
{
// Update current position for the main thread use.
RECT r = wxGetWindowRect(hwnd);
sharedData->m_winPosition = wxPoint(r.left, r.top);
}
return TRUE;
if ( endDialog )
break;
return S_FALSE;
}
} // Leave shared data critical section.
if ( endDialog )
{
::EndDialog( hwnd, IDCLOSE );
return S_FALSE;
}
// Return anything.
return 0;
return S_OK;
}
#endif // wxHAS_MSW_TASKDIALOG

View File

@@ -986,10 +986,11 @@ void wxTopLevelWindowMSW::SetIcons(const wxIconBundle& icons)
DoSelectAndSetIcon(icons, SM_CXICON, SM_CYICON, ICON_BIG);
}
bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
// static
bool wxTopLevelWindowMSW::MSWEnableCloseButton(WXHWND hwnd, bool enable)
{
// get system (a.k.a. window) menu
HMENU hmenu = GetSystemMenu(GetHwnd(), FALSE /* get it */);
HMENU hmenu = GetSystemMenu(hwnd, FALSE /* get it */);
if ( !hmenu )
{
// no system menu at all -- ok if we want to remove the close button
@@ -1008,7 +1009,7 @@ bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
return false;
}
// update appearance immediately
if ( !::DrawMenuBar(GetHwnd()) )
if ( !::DrawMenuBar(hwnd) )
{
wxLogLastError(wxT("DrawMenuBar"));
}
@@ -1016,6 +1017,11 @@ bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
return true;
}
bool wxTopLevelWindowMSW::EnableCloseButton(bool enable)
{
return MSWEnableCloseButton(GetHwnd(), enable);
}
// Window must have wxCAPTION and either wxCLOSE_BOX or wxSYSTEM_MENU for the
// button to be visible. Also check for wxMAXIMIZE_BOX because we don't want
// to enable a button that is excluded from the current style.