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;
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;
CDlgTimedMessageBox::m_mapTimerIdToClassMe.Lookup((void*)idEvent,
(void *&) pMe);
if( pMe!=NULL )
pMe->LocalTimerProc();
}
void CDlgTimedMessageBox::LocalTimerProc(void)
{
if( too long running )
{
}
else
{
}
}
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