子窗口处于焦点状态时未调用WinAPI计时器回调

我正在使用Direct3D和WinAPI开发3D编辑器应用程序。我创建一个主窗口,其中有一个子窗口,该子窗口占据了主窗口客户区的一部分。 D3D将其用作渲染目标。然后,我还使用CreateWindow创建一个单独的窗口,该窗口的构建方式与主窗口相同(即,“主窗口”和用作渲染目标的内部子窗口),并将该窗口作为子窗口。主应用程序窗口(以确保将它们最小化/还原/一起关闭)。

D3D渲染由渲染目标子窗口处理其WM_PAINT消息来执行。为了减少不必要的开销,我将窗口过程设置为仅在WM_PAINTGetForegroundWindow与相应的窗口句柄匹配时在GetFocus上呈现。换句话说,我只希望刷新位于顶部且聚焦的窗口渲染。

这是我的主要消息循环:

HWND mainWnd;
HWND mainRenderWnd;
HWND childWnd;
HWND childRenderWnd;

// ...

MSG msg = {};
while (WM_QUIT != msg.message)
{
    if (PeekMessage(&msg,nullptr,PM_REMOVE))
    {
        if (!Translateaccelerator(mainWnd,accel,&msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
    {
        // Run non-UI code...
    }
}

当主窗口获得WM_setfOCUS时,我将焦点设置为其渲染目标子窗口,因为我要在那里处理输入(例如相机控件):

// ...
LRESULT CALLBACK MainWndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
    switch(uMsg)
    {
        //...
        case WM_setfOCUS:
        {
            setfocus(mainRenderWnd);
            return 0;
        }
        //...
    }
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

我在childWnd的窗口过程中执行相同的操作,将焦点设置为childRenderWnd。该窗口由用户打开和关闭,即在任何时候它可能存在或可能不存在,但是当它存在并且没有最小化时,它必须是具有焦点的前景窗口。另外,为了控制子窗口的帧率,我使用计时器刷新它:

static constexpr UINT_PTR RENDER_TIMER_ID = (UINT_PTR)0x200;
void TimerCallback(HWND Arg1,UINT Arg2,UINT_PTR Arg3,DWORD Arg4)
{
    if (IsIconic(childWnd) || !(GetFocus() == childRenderWnd))
        return;

    // Invalidate the render area to make sure it gets redrawn
    InvalidateRect(childRenderWnd,false);
}

// ...

SetTimer(childWnd,RENDER_TIMER_ID,16,(TIMERPROC)TimerCallback);

完成所有这些设置后,mainWndmainRenderWnd似乎工作正常。但是,childRenderWnd处于前景和焦点时,拒绝渲染任何内容。在调试时,我发现虽然是这种情况,但计时器回调从未执行,也没有WM_TIMER消息被调度到子窗口。

另一方面,当我故意将焦点移出子窗口并移至主窗口(同时保持打开状态)时,将发送计时器消息,并执行回调。另一个问题是,当我在两个窗口都打开时最小化应用程序,然后又将它们都还原时,两个窗口的渲染目标都不会刷新。取而代之的是,焦点似乎“翻转了”,因为我必须先单击子窗口,然后单击主窗口,然后才能正确刷新(而子窗口仍然拒绝渲染任何东西)。

我想念什么?我搜索了其他有问题的消息,例如错误的消息泵设置阻止了WM_TIMER,但是似乎什么也无法解释这里的情况。

预先感谢您的帮助!

zmw7152 回答:子窗口处于焦点状态时未调用WinAPI计时器回调

IInspectableRaymond Chen的评论帮助我找到了答案。我试图在一个最小的应用程序中重现错误,最后发现了麻烦的根源。最初,如果没有要发送的消息,则主窗口将在消息循环中仅调用InvalidateRect,从而有效地重绘自身。原来这似乎没有造成任何伤害,场景渲染得很好,并且窗口响应了输入。但是,一旦我引入了第二个窗口,它肯定会在消息循环中充满绘画消息,从而使任何计时器消息都无法通过。

非常简单的解决方案是为两个窗口都提供一个计时器,以设置刷新D3D渲染目标的速率。结合焦点检查,我现在可以轻松地在两个窗口之间切换,在任何给定时间都只有一个刷新。

问题仍然是WinAPI计时器系统是否是最佳选择。除了我的错误代码,人们还提到计时器的消息优先级低,从长远来看,这可能使其成为帧率控制的不佳选择。

编辑:我最终得到了IInspectable的建议,即在主消息循环中使用while循环来处理所有消息的分发,处理完这些消息后,我将执行帧更新。这样一来,我就可以持续更新所有窗口并控制帧速率,而不会冒窗口消息被卡住的风险。

本文链接:https://www.f2er.com/3165489.html

大家都在问