WTL CIdleHandler 的正确使用方法是什么?

3
我将尝试学习WTL/Win32编程,但我并不完全理解CIdleHandler mixin类的设计。
对于WTL 9.1版本,CMessageLoop代码如下(来自atlapp.h):
for(;;)
    {
        while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if(!OnIdle(nIdleCount++))
                bDoIdle = FALSE;
        }

        bRet = ::GetMessage(&m_msg, NULL, 0, 0);

        if(bRet == -1)
        {
            ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n"));
            continue;   // error, don't process
        }
        else if(!bRet)
        {
            ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n"));
            break;   // WM_QUIT, exit message loop
        }

        if(!PreTranslateMessage(&m_msg))
        {
            ::TranslateMessage(&m_msg);
            ::DispatchMessage(&m_msg);
        }

        if(IsIdleMessage(&m_msg))
        {
            bDoIdle = TRUE;
            nIdleCount = 0;
        }
    }

实际调用空闲处理程序非常简单。
// override to change idle processing
virtual BOOL OnIdle(int /*nIdleCount*/)
{
    for(int i = 0; i < m_aIdleHandler.GetSize(); i++)
    {
        CIdleHandler* pIdleHandler = m_aIdleHandler[i];
        if(pIdleHandler != NULL)
            pIdleHandler->OnIdle();
    }
    return FALSE;   // don't continue
}

现在我们需要调用IsIdleMessage函数

static BOOL IsIdleMessage(MSG* pMsg)
{
    // These messages should NOT cause idle processing
    switch(pMsg->message)
    {
    case WM_MOUSEMOVE:
#ifndef _WIN32_WCE
    case WM_NCMOUSEMOVE:
#endif // !_WIN32_WCE
    case WM_PAINT:
    case 0x0118:    // WM_SYSTIMER (caret blink)
        return FALSE;
    }

    return TRUE;
}

我的分析如下:在“PeekMessage Drought”(Win32应用程序没有接收到消息的时间段)期间,OnIdle处理程序被调用一次。
但为什么只有一次呢?在PeekMessage情况下,难道不希望后台空闲任务不断地被调用吗?此外,对我来说,WM_LBUTTONDOWN(用户在窗口上单击某物)会激活空闲处理(bDoIdle = True),但明确禁止调用WM_MOUSEMOVE以防止重新激活空闲处理,这似乎很奇怪。
有人可以给我WTL空闲循环(或更具体地说:CIdleHandler)的“正确”使用场景吗?我猜我的期望是空闲处理函数将是小的、增量式的任务,不超过100ms即可完成。然后它们会在后台重复调用。
但在WTL中似乎不是这种情况。或者我没有完全理解空闲循环?因为如果我将增量式后台任务注册为CIdleHandler……那么如果用户离开窗口,任务只会运行一次!如果没有向系统发送任何消息(例如WM_LBUTTONDOWN),则bDoIdle变量将永远保持false!
有人能对这一切做出好的解释吗?

3
空闲处理程序的设计仅用于更新用户界面状态,例如启用/禁用工具栏上的按钮,如果活动文档发生更改。 - RbMm
1
我不太幸运地解释为什么在没有任何事情发生时应该运行代码,但是[这里有一个例子](https://dev59.com/b2XWa4cB1Zd3GeqPO6Mg#11294115)。 - Hans Passant
1个回答

4
正如评论中所说,OnIdle处理程序应在特定活动后开始空闲时调用,特别是为了更新UI。这解释了处理程序的“一次”调用:发生了某些事情,然后您有机会一次性更新UI元素。如果您需要进行持续的后台处理,则应使用计时器或工作线程。

WTL示例建议使用空闲处理程序,例如在\Samples\Alpha\mainfrm.h中。

窗口类捕获线程的消息循环并请求空闲更新:

LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
    // ...

    // register object for message filtering and idle updates
    CMessageLoop* pLoop = _Module.GetMessageLoop();
    ATLASSERT(pLoop != NULL);
    pLoop->AddMessageFilter(this);
    pLoop->AddIdleHandler(this);

在消息处理和用户交互后,空闲处理程序将更新工具栏以反映可能的状态更改。
virtual BOOL OnIdle()
{
    UIUpdateToolBar();
    return FALSE;
}

1
这个解释(以及其他评论)非常有道理。MFC文档上的空闲处理与您的说法相矛盾:https://learn.microsoft.com/en-us/cpp/mfc/idle-loop-processing,但在这种情况下我相信您的建议胜过文档。利用新线程/定时器来进行“漫长的后台处理”更加合理。 - Dragontamer5788

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接