|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
OverviewA delay message box will not allow the user to dismiss it until the delay
interval has run out. It does this by disabling the OK button on the message box
till the specified time interval expires. One example of a situation where this
might be useful is a shareware program that has expired its trial period. Say you
want to show a message box to the user and you want to make sure it stays there
for at least 10 seconds. Anyway Nish started writing this class with a totally
different idea. He wanted to center his message box on it's parent window. That's
when he found out that, message boxes do this by default. In his case they were
not doing so because he had made them owned by the desktop. Anyway Nish ended up
writing a delay message box that also has an auto-close option. If the
auto-close option is set to What turned out as a simple attempt to center a message box has ended up in
some rather complicated code with That's when Shog got interested in the class. Shog is the type of guy who
hates any kind of code obfuscation and he's always trying to figure out easier
ways of doing things. Anyhow he modified Nish's class so that it was more
MFC-ied. We decided to call it
UsageThe ConstructorConstructs a
Note - In MessageBox methodDisplays the message box.
Sample Code/* You may use either of the classes. In behaviour they are identical. It's in the implementation that they differ. */ //CDelayMessageBox mbox(this); CDelayMessageBox2 mbox(this); mbox.MessageBox(m_text, m_delay, m_close,(CDelayMessageBox2::MBIcon)mbicon); Technical detailsCDelayMessageBox
When the The custom CDelayMessageBox2Now we don't have a hidden The timer proc is similar to the timer proc in the original class and we keep
changing the title text to reflect the remaining time in seconds. Once the delay
interval has elapsed we post a Class source listingsBoth the old and new classes are listed here. They both use contrastingly different techniques to solve the delay message box problem. We thought you might want to compare them and that might also help to understand the inner workings better. Now the class has out-valued itself in the sense, it is now a class with a lot more academic value than utility. Both the implementations reveal a lot about the inner workings of Windows. Header filesOld one#pragma once // COkayWnd class COkayWnd : public CWnd { DECLARE_DYNAMIC(COkayWnd) public: COkayWnd(); virtual ~COkayWnd(); protected: DECLARE_MESSAGE_MAP() public: protected: virtual LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam); }; class CDelayMessageBox : public CWnd { DECLARE_DYNAMIC(CDelayMessageBox) public: CDelayMessageBox(CWnd* pParent); virtual ~CDelayMessageBox(); enum MBIcon { MBICONNONE = 0, MBICONSTOP = MB_ICONSTOP, MBICONQUESTION = MB_ICONQUESTION, MBICONEXCLAMATION = MB_ICONEXCLAMATION, MBICONINFORMATION = MB_ICONINFORMATION }; int MessageBox(LPCTSTR lpszText, int count, bool bclose = false, MBIcon icon = MBICONNONE ); protected: HHOOK m_hHook; HWND m_hMsgBoxWnd; HWND m_hOK; int m_count; bool m_autoclose; COkayWnd m_OkayWnd; static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam); static BOOL CALLBACK EnumChildProc( HWND hwnd, LPARAM lParam ); static CMapPtrToPtr m_map; CString FormTitle(int num); protected: DECLARE_MESSAGE_MAP() public: afx_msg void OnTimer(UINT nIDEvent); }; New one#pragma once class CDelayMessageBox2 : public CWnd { DECLARE_DYNAMIC(CDelayMessageBox2) public: CDelayMessageBox2(CWnd* pParent); enum MBIcon { MBICONNONE = 0, MBICONSTOP = MB_ICONSTOP, MBICONQUESTION = MB_ICONQUESTION, MBICONEXCLAMATION = MB_ICONEXCLAMATION, MBICONINFORMATION = MB_ICONINFORMATION }; int MessageBox(LPCTSTR lpszText, int count, bool bclose = false, MBIcon icon = MBICONNONE ); protected: int m_count; bool m_autoclose; HWND m_hWndParent; CString FormTitle(int num); virtual LRESULT DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnSubclassedInit(WPARAM wParam, LPARAM lParam); afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct); afx_msg void OnTimer(UINT nIDEvent); DECLARE_MESSAGE_MAP() }; C++ implementation filesOld one#include "stdafx.h" #include "DelayMessageBox.h" CMapPtrToPtr CDelayMessageBox::m_map; IMPLEMENT_DYNAMIC(CDelayMessageBox, CWnd) CDelayMessageBox::CDelayMessageBox(CWnd* pParent) { m_hHook = NULL; m_hMsgBoxWnd = NULL; m_hOK = NULL; m_autoclose = NULL; m_OkayWnd.m_hWnd = NULL; Create(NULL, "{8B32A21C-C853-4785-BE20-A4E575EE578A}", WS_OVERLAPPED, CRect(0,0,0,0), pParent,1000); m_map[m_hWnd] = this; } CDelayMessageBox::~CDelayMessageBox() { m_map.RemoveKey(m_hWnd); DestroyWindow(); } BEGIN_MESSAGE_MAP(CDelayMessageBox, CWnd) ON_WM_TIMER() END_MESSAGE_MAP() BOOL CALLBACK CDelayMessageBox::EnumChildProc( HWND hwnd, LPARAM lParam ) { CDelayMessageBox *pthis = static_cast<CDelayMessageBox*>((LPVOID)lParam); char str[256]; ::GetWindowText(hwnd,str,255); if(strcmp(str,"OK") == 0) { pthis->m_hOK = hwnd; if(pthis->m_count>0) { ::EnableWindow(pthis->m_hOK,FALSE); } return FALSE; } return TRUE; } LRESULT CALLBACK CDelayMessageBox::CBTProc( int nCode,WPARAM wParam, LPARAM lParam) { if (nCode == HCBT_ACTIVATE ) { void* p; m_map.Lookup(::FindWindowEx(::GetParent( (HWND)wParam),NULL,NULL, "{8B32A21C-C853-4785-BE20-A4E575EE578A}"),p); CDelayMessageBox* pthis = (CDelayMessageBox*)p; pthis->m_hMsgBoxWnd = (HWND)wParam; EnumChildWindows(pthis->m_hMsgBoxWnd, EnumChildProc,(LPARAM)pthis); UnhookWindowsHookEx(pthis->m_hHook); if(pthis->m_count>0) pthis->m_OkayWnd.SubclassWindow( pthis->m_hMsgBoxWnd); pthis->m_hHook = NULL; } return FALSE; } void CDelayMessageBox::OnTimer(UINT nIDEvent) { if(nIDEvent == 100 && m_hMsgBoxWnd ) { if(m_count>0) m_OkayWnd.SetWindowText(FormTitle(--m_count)); if(m_count == 0) { if(m_OkayWnd.m_hWnd) { m_OkayWnd.UnsubclassWindow(); m_OkayWnd.m_hWnd = NULL; } ::EnableWindow(m_hOK,TRUE); KillTimer(100); m_hOK = NULL; if(m_autoclose) ::PostMessage(m_hMsgBoxWnd,WM_CLOSE,0,0); m_hMsgBoxWnd = NULL; } } CWnd::OnTimer(nIDEvent); } int CDelayMessageBox::MessageBox(LPCTSTR lpszText, int count, bool bclose,MBIcon icon) { m_autoclose = bclose; m_hHook = SetWindowsHookEx(WH_CBT,CBTProc, AfxGetApp()->m_hInstance, AfxGetApp()->m_nThreadID); m_count = count; SetTimer(100,1000,NULL); CWnd::MessageBox(lpszText,FormTitle(m_count),icon); return IDOK; } CString CDelayMessageBox::FormTitle(int num) { CString s; s.Format("%d seconds remaining",num); return s; } // COkayWnd IMPLEMENT_DYNAMIC(COkayWnd, CWnd) COkayWnd::COkayWnd() { } COkayWnd::~COkayWnd() { } BEGIN_MESSAGE_MAP(COkayWnd, CWnd) END_MESSAGE_MAP() // COkayWnd message handlers LRESULT COkayWnd::DefWindowProc(UINT message, WPARAM wParam, LPARAM lParam) { if(message == WM_COMMAND) { if(HIWORD(wParam) == BN_CLICKED ) return 0; } return CWnd::DefWindowProc(message, wParam, lParam); } New one#include "stdafx.h" #include "DelayMessageBox2.h" #include <afxpriv.h> IMPLEMENT_DYNAMIC(CDelayMessageBox2, CWnd) CDelayMessageBox2::CDelayMessageBox2(CWnd* pParent) { m_hWndParent = pParent->GetSafeHwnd(); // can be NULL m_autoclose = NULL; m_count = 0; } BEGIN_MESSAGE_MAP(CDelayMessageBox2, CWnd) ON_WM_TIMER() ON_WM_CREATE() ON_MESSAGE(WM_INITDIALOG, OnSubclassedInit) END_MESSAGE_MAP() // Purpose: Unhook window creation int CDelayMessageBox2::OnCreate( LPCREATESTRUCT lpCreateStruct) { AfxUnhookWindowCreate(); return CWnd::OnCreate(lpCreateStruct); } // Purpose: Disable OK button, start timer LRESULT CDelayMessageBox2::OnSubclassedInit( WPARAM wParam, LPARAM lParam) { LRESULT lRet = Default(); CWnd* pOk = GetDlgItem(IDCANCEL); if ( NULL != pOk ) pOk->EnableWindow(FALSE); SetTimer(100,1000,NULL); return lRet; } // Purpose: display running countdown, close when finished. void CDelayMessageBox2::OnTimer(UINT nIDEvent) { if (nIDEvent == 100) { if (m_count>0) SetWindowText(FormTitle(--m_count)); if (m_count == 0) { CWnd* pOk = GetDlgItem(IDCANCEL); if ( NULL != pOk ) { pOk->EnableWindow(TRUE); pOk->SetFocus(); } KillTimer(100); if (m_autoclose) PostMessage(WM_CLOSE,0,0); } } } // Purpose: Display a message box, hooking it to do stuff int CDelayMessageBox2::MessageBox(LPCTSTR lpszText, int count, bool bclose,MBIcon icon) { m_autoclose = bclose; m_count = count; AfxHookWindowCreate(this); return ::MessageBox(m_hWndParent, lpszText, FormTitle(m_count), icon); } // Purpose: compose a title for the dialog based // on the # of seconds left to disable it CString CDelayMessageBox2::FormTitle(int num) { CString s; s.Format("%d seconds remaining",num); return s; } // Purpose: prevent dialog from closing before // it has timed out LRESULT CDelayMessageBox2::DefWindowProc( UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_COMMAND && m_count > 0) { if(HIWORD(wParam) == BN_CLICKED ) return 0; } return CWnd::DefWindowProc(message, wParam, lParam); } ConclusionThis class started off with one idea and ended up with another. This was also one of Nish's first proper attempts with using hooks. So he might have made some erroneous assumptions. But he is counting on the wonderful feedback that is available through the thousands of CodeProject visitors and regulars. Shog would also like to see whether there is any way to further simply the class. Thank you. Updates and fixes
| ||||||||||||||||||||