65.9K
CodeProject 正在变化。 阅读更多。
Home

线程安全的定时消息框

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.43/5 (4投票s)

2000 年 11 月 10 日

viewsIcon

225994

downloadIcon

3455

系统消息框, 在一段时间后自动关闭。

代码是关于什么的

这是为了使用系统的 MessageBox(无需创建额外的对话框)。在没有用户响应一段时间后,对话框将自动关闭。提供了一个可选的文本,显示剩余的交互时间。它也是 MSDN Q181934 的替代方案(该方案无法正常工作)。

基础

在调用 MessageBox 之前,会安装一个计时器。在计时器的回调过程中,消息框中的消息文本会被替换(可选)。

当应该关闭对话框时,会向 MessageBox 窗口发布一个 WM_COMMAND

计时器回调

我决定将所有与 MessageBox 相关的的信息(标题、消息、标志...)收集到一个类中。然后就出现了问题,只有静态计时器回调过程才能使用 SetTimer() 函数安装:无法访问类成员(因为它静态)。

为了解决这个问题,我插入了一个 Map 来存储与计时器 ID 对应的类。

标题

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

来源

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    
    }    
}

查找 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);
}

 

将其放入一个函数中

有一个函数你可以用来调用消息框,而无需创建类实例:CDlgTimedMessageBox::TimedMessageBox() 会为你完成剩下的事情!

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

你可以传递一个 BOOL * 来获取信息,如果用户按下了按钮。

 

线程安全

Map 访问被 CCriticalSection 包围。

示例

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

历史

5.11.2000    发布到 Code Project
14.11.2000  修复了 NT4 的 bug
05.02.2001  报告了在 MBYES 创建时出现的 bug:默认按钮变成了 IDCANCEL
05.02.2001  全局函数移动到类作用域

© . All rights reserved.