A thread-safe timed message box






3.43/5 (4 votes)
Nov 10, 2000

224623

3455
The system Message Box that is closed atuomatically after some time
What is the code for
This is for using the systems MessageBox (no extra dialog creation needed). After a time with no user-response the dialog is automatically closed. An optional text showing the time left to interact is provided. It's also a replacement for MSDN Q181934 (doesn't work)
Basics
Before calling MessageBox
, a timer is installed. In the timer's
callback-procedure the message-text from the box is replaced (optional).
When the box should be closed, a WM_COMMAND
is posted to the
MessageBox Window.
Timer-Callback
I decided to collect all the information corresponding to the MessageBox
(title, Message, Flags ...) in a class. Then there is the problem that only a
static timer-callback-procedure can be installed with the SetTimer()
-function:
one can not access the class-members (as it is static).
To solve this problem, I inserted a Map to store the classes corresponding to a timer-id:
Header:
class CDlgTimedMessageBox { public: // ... UINT ShowMessageBox(BOOL *pbStoppedByUser=NULL); // ... static void CALLBACK GlobalTimerProc(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime); void LocalTimerProc(void); // ... static CMapPtrToPtr m_mapTimerIdToClassMe; // to call the messagebox within one line ! static UINT TimedMessageBox(...); protected: UINT m_idTimer; };
Source:
UINT CDlgTimedMessageBox::ShowMessageBox(BOOL *pbStoppedByUser) { // ... m_idTimer = ::SetTimer(NULL, 0, 1000, (TIMERPROC) CDlgTimedMessageBox::GlobalTimerProc); CDlgTimedMessageBox::m_mapTimerIdToClassMe.SetAt((void*)m_idTimer, this); // ... ::MessageBox(...) // ... } void CALLBACK CDlgTimedMessageBox::GlobalTimerProc(HWND hwnd, UINT uiMsg, UINT idEvent, DWORD dwTime) { CDlgTimedMessageBox *pMe = NULL; // Find the corresponding class by the timer-id CDlgTimedMessageBox::m_mapTimerIdToClassMe.Lookup((void*)idEvent, (void *&) pMe); if( pMe!=NULL ) pMe->LocalTimerProc(); } void CDlgTimedMessageBox::LocalTimerProc(void) { // find the Message-Box-window // Calculate time since start if( too long running ) { // Stop MessageBox } else { // replace text of MessageBox } }
Finding the MessageBox
hWnd = ::GetWindow(::GetDesktopWindow(), GW_CHILD); while( (hWnd!=NULL) && (m_hMsgBox==NULL) ) { pWnd = CWnd::FromHandle(hWnd); pWnd->GetWindowText(title); if( AfxIsDescendant(m_hParent, hWnd) && ::IsWindowVisible(hWnd) && (m_Title.CompareNoCase(title)==0) ) { m_hMsgBox = hWnd; break; } hWnd = ::GetWindow(hWnd, GW_HWNDNEXT); }
Put it in one function
There is one function you can use to call the messagebox without creating
a class-instance: CDlgTimedMessageBox::TimedMessageBox()
does
the rest for you!
UINT CDlgTimedMessageBox::TimedMessageBox(UINT flags, LPCTSTR ptszMessage,
LPCTSTR ptszTitle,
DWORD dwTimeout, UINT dDefaultReturn,
LPCTSTR ptszMessageTimer, HWND hwndParent,
BOOL *pbStoppedByUser)
{
CDlgTimedMessageBox msgBox(flags, ptszMessage, ptszTitle,
dwTimeout, dDefaultReturn,
ptszMessageTimer, hwndParent);
return msgBox.ShowMessageBox(pbStoppedByUser);
}
You can pass a BOOL *
to get the info, if the user has
pressed a button.
Thread-Safe
The map-access is enclosed by a CCriticalSection.
Sample
BOOL stoppedByUser; UINT erg; erg = CDlgTimedMessageBox::TimedMessageBox(MB_YESNO|MB_ICONHAND, "Please press a button", "box-title", 5000, IDYES, "\nin the next %lu sec !", NULL, &stoppedByUser);
History
5.11.2000 posted to Code Project
14.11.2000 bugfixes for NT4
05.02.2001 bug reported when created with MBYES: defbutton then
is IDCANCEL
05.02.2001 global function went to class-scope