Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / MFC
Article

Delay MessageBox with auto-close option

,
Rate me:
Please Sign up or sign in to vote.
4.97/5 (28 votes)
13 Aug 2002Ms-PL7 min read 292.6K   5.4K   73   52
This message box delays its dismissal by disabling the OK button for a delay interval. It also has an optional auto-close feature. There are two versions, one uses a WH_CBT hook and some basic window tricks like sub-classing to achieve its goal. The other one is more MFC-ied.

Overview

A 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 true, then the message box will close on it's own once the delay period has terminated. This was how the CDelayMessageBox class was born.

What turned out as a simple attempt to center a message box has ended up in some rather complicated code with WH_CBT hooks, invisible windows, a CWnd* to HWND map and a sub-classed message box window that overrides DefWindowProc of all things to override. It sure seems like a lot of work for such a simple sounding task. But it is the write-once-use-multiple-times kind of class and thus Nish hopes his methods are justified.

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 CDelayMessageBox2 because while it didn't extend the class in anyway, the implementation was thoroughly revamped. Both the classes are presented in this article as well as in the demo project and the class source. You'll find the following files to be of interest to you.

  • DelayMessageBox.cpp - This was the original implementation file and you can take a look at this one if you are interested in seeing elementary examples of the use of WH_CBT hooks and window sub-classing.
  • DelayMessageBox.h - The header file for the original class
  • DelayMessageBox2.cpp  - This is the new implementation file and you can see some high quality MFC type sub classing here.
  • DelayMessageBox2.h - The header file for the revised class.

Usage

The CDelayMessageBox and CDelayMessageBox2 classes have only one public method in addition to the constructor. There is no parameter-less constructor. By the way, in the rest of the article when you see CDelayMessageBox, it represents both the classes unless specifically mentioned otherwise.

Constructor

Constructs a CDelayMessageBox object.

CDelayMessageBox(CWnd* pParent);

pParent - This will be the parent window of the eventual message box that will be displayed. You should not set this to NULL. The parent window must be a valid CWnd that holds a valid HWND.

Note - In CDelayMessageBox2 you can set pParent to NULL.

MessageBox method

Displays the message box.

int MessageBox(<br>
    LPCTSTR lpszText, <br>
    int count, <br>
    bool bclose = false, <br>
    MBIcon icon = MBICONNONE );

lpszText - Points to a null-terminated string containing the message to be displayed. You may use a CString here.

count - This is the delay in seconds. You can use any delay from 0 - the maximum size of an int, but you are advised to keep it under 60 for all practical purposes.

bclose - If this is set to true, the message box will close on its own after the delay period, otherwise the OK button is enabled so that the user can dismiss the message box manually.

icon - This is an MBIcon enumeration which can take one of the following values.

  • CDelayMessageBox::MBIcon::MBICONNONE
  • CDelayMessageBox::MBIcon::MBICONSTOP
  • CDelayMessageBox::MBIcon::MBICONQUESTION
  • CDelayMessageBox::MBIcon::MBICONEXCLAMATION
  • CDelayMessageBox::MBIcon::MBICONINFORMATION

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 details

CDelayMessageBox

CDelayMessageBox is derived from CWnd and it creates a CWnd object in it's constructor. The window that is created has a unique title text and is hidden. The unique text is a GUID. The class has a static CMapPtrToPtr member using which we maintain an HWND to CWnd* map. This is so that any number of threads may simultaneously use the CDelayMessageBox  class. In other words it's thread-safe.

When the MessageBox method is called we use SetWindowsHookEx to set a WH_CBT hook. We then start a timer at a 1-second interval and use CWnd::MessageBox to show our message box. In the hook proc we enumerate all the child windows of the message box window using EnumChildWindows. In the callback for EnumChildWindows, we disable the OK button. We also subclass the message box window to a custom CWnd derived class. And we also unhook the WH_CBT hook. In the timer proc, we keep decreasing the count and also keep changing the title text of the message box to reflect the remaining time in seconds. When the count reaches zero we enable the OK button or if the auto-close option is true we dismiss the message box using a WM_CLOSE message.

The custom CWnd derived class into which we subclass the message box was added as a bug fix to a problem reported by Andreas Saurwein where he found that the message box can be closed using the space bar. This is because a WM_COMMAND message is sent to the message box window with a

BN_CLICKED
notification when the space bar is pressed. This has been handled by overriding DefWindowProc and filtering out this message.

CDelayMessageBox2

Now we don't have a hidden CWnd parent for the message box. We create the CWnd object using AfxHookWindowCreate when the call to MessageBox(...) is made. MFC will call

SetWindowsHookEx
for you. Now the message box has been sub-classed by our CWnd derived class. We override OnSubclassedInit and we disable the OK button and start our timer. We also override OnCreate where we call AfxUnhookWindowCreate as we don't have any further need for the hook.

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 WM_CLOSE to the message box. And also enable the OK button.

Class source listings

Both 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 files

Old 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 files

Old 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);
}

Conclusion

This 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

  • Aug 14 2002 - Shog has joined Nish as co-author and now there is a more MFC-ied version of the class available. Both classes have been retained as they both depict various interesting win32 techniques.
  • Aug 13 2002 - A bug was reported by Andreas Saurwein where he discovered that the space bar can close the delayed message box. This has been fixed by sub-classing the message box window and handling the message that causes this behaviour.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Written By
Software Developer
United States United States
Poke...

Comments and Discussions

 
QuestionClose the MessageBox when exit program Pin
forzamilan21-Sep-05 17:25
forzamilan21-Sep-05 17:25 
Hi, my problem is: When I call to show a MessageBox, and then exit the program (by DestroyWindow the main dialog). The MessageBox remain and the program will not exit until I press the OK button of message box. How can I program to close the openning message box when exit main program?
Thanks.
QuestionWhy annoying the user? Pin
uweph4-Apr-05 21:50
uweph4-Apr-05 21:50 
AnswerRe: Why annoying the user? Pin
originSH20-Jul-07 1:53
originSH20-Jul-07 1:53 
Generalvery urgent Pin
nandeeshds10-Jan-05 3:47
nandeeshds10-Jan-05 3:47 
GeneralRe: very urgent Pin
Nish Nishant10-Jan-05 20:02
sitebuilderNish Nishant10-Jan-05 20:02 
GeneralRe: very urgent Pin
nandeeshds11-Jan-05 2:09
nandeeshds11-Jan-05 2:09 
GeneralRequest Pin
Member 26654617-Nov-04 20:48
Member 26654617-Nov-04 20:48 
Generala doubt ... Pin
RaajaOfSelf10-Nov-04 9:48
RaajaOfSelf10-Nov-04 9:48 
QuestionClose on focus lost? Pin
Anonymous28-Aug-04 13:46
Anonymous28-Aug-04 13:46 
GeneralAny better way to get the MB_ICONEXCLAMATION ... Pin
xcavin19-Jul-04 21:54
xcavin19-Jul-04 21:54 
GeneralRe: Any better way to get the MB_ICONEXCLAMATION ... Pin
Shog920-Jul-04 5:55
sitebuilderShog920-Jul-04 5:55 
Questioncan I enable the ok button for ever? Pin
l1t25-Mar-03 14:06
l1t25-Mar-03 14:06 
AnswerRe: can I enable the ok button for ever? Pin
Nish Nishant27-Mar-03 20:01
sitebuilderNish Nishant27-Mar-03 20:01 
GeneralUi Consideration Pin
Swinefeaster19-Aug-02 10:48
Swinefeaster19-Aug-02 10:48 
GeneralRe: Ui Consideration Pin
Shog919-Aug-02 11:17
sitebuilderShog919-Aug-02 11:17 
GeneralRe: Ui Consideration Pin
Swinefeaster20-Aug-02 10:55
Swinefeaster20-Aug-02 10:55 
GeneralSetTimer Pin
Bob Eastman14-Aug-02 3:37
Bob Eastman14-Aug-02 3:37 
GeneralRe: SetTimer Pin
Nish Nishant14-Aug-02 3:49
sitebuilderNish Nishant14-Aug-02 3:49 
GeneralRe: SetTimer Pin
Bob Eastman14-Aug-02 4:47
Bob Eastman14-Aug-02 4:47 
GeneralRe: SetTimer Pin
Nish Nishant14-Aug-02 5:33
sitebuilderNish Nishant14-Aug-02 5:33 
GeneralRe: SetTimer Pin
Bob Eastman14-Aug-02 6:38
Bob Eastman14-Aug-02 6:38 
GeneralRe: SetTimer Pin
Nish Nishant14-Aug-02 18:03
sitebuilderNish Nishant14-Aug-02 18:03 
GeneralGotcha! Bug. Pin
Andreas Saurwein13-Aug-02 2:36
Andreas Saurwein13-Aug-02 2:36 
GeneralRe: Gotcha! Bug. Pin
Nish Nishant13-Aug-02 5:37
sitebuilderNish Nishant13-Aug-02 5:37 
QuestionSimilar article? Pin
Anthony_Yio13-Aug-02 0:53
Anthony_Yio13-Aug-02 0:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.