Обратный вызов таймера WinAPI не вызывается, когда дочернее окно находится в фокусе - PullRequest
0 голосов
/ 04 ноября 2019

Я работаю над приложением 3D-редактора, используя Direct3D и WinAPI. Я создаю главное окно, которое имеет дочернее окно, которое занимает часть клиентской области главного окна. Это используется D3D в качестве цели рендеринга. Затем я также создаю отдельное окно с CreateWindow, которое строится так же, как и основное окно (т. Е. «Главное окно» и внутренний дочерний элемент, используемый в качестве цели рендеринга), и я делаю это окно дочерним по отношению к основному. окно приложения (чтобы минимизировать / восстановить / закрыть их вместе).

Рендеринг D3D выполняется дочерними окнами рендеринга, обрабатывающими их сообщения WM_PAINT. Чтобы уменьшить ненужные накладные расходы, я настроил оконные процедуры так, чтобы они отображались только на WM_PAINT, если GetForegroundWindow и GetFocus совпадают с соответствующими дескрипторами окна. Другими словами, я хочу, чтобы рендеринг окна обновлялся только в том случае, если он находится сверху и сфокусирован.

Это мой основной цикл сообщений:

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

// ...

MSG msg = {};
while (WM_QUIT != msg.message)
{
    if (PeekMessage(&msg, nullptr, 0, 0, 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, nullptr, false);
}

// ...

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

Со всей этой настройкой mainWnd и mainRenderWnd, кажется, работают просто отлично. Однако childRenderWnd отказывается отображать что-либо, когда оно находится на переднем плане и в фокусе. Во время отладки я обнаружил, что, хотя это так, обратный вызов таймера никогда не выполняется, и сообщение WM_TIMER не отправляется в дочернее окно.

С другой стороны, в тот момент, когда я намеренно перемещаю фокус из дочернего окна в главное окно (при сохранении обоих открытых), отправляется сообщение таймера и выполняется обратный вызов. Другая проблема заключается в том, что когда я минимизирую приложение, когда оба окна открыты, а затем восстанавливаю их оба, цель рендеринга ни одного из окон не обновляется. Вместо этого кажется, что фокус «перевернулся», так как я должен сначала щелкнуть по моему дочернему окну, а затем по главному окну, и это заставляет его обновиться должным образом (в то время как ребенок все еще отказывается что-либо визуализировать).

Чего мне не хватает? Я искал других, у которых возникла проблема, например, неправильная блокировка установки сообщения WM_TIMER, но, кажется, ничто не объясняет, что здесь происходит.

Заранее спасибо за помощь!

1 Ответ

0 голосов
/ 05 ноября 2019
Комментарии

IInspectable и Raymond Chen помогли мне прийти к ответу. Я попытался воспроизвести ошибку в минимальном приложении и, наконец, наткнулся на источник моей проблемы. Первоначально главное окно просто вызывало InvalidateRect в цикле сообщений, если не было отправляемых сообщений, эффективно перерисовывая себя при каждой возможности. Первоначально это, казалось, не причиняло никакого вреда, сцена была отрисована просто отлично, и окно реагировало на входные данные. Однако после того, как я ввел второе окно, оно, должно быть, затопило цикл сообщений сообщениями рисования, что сделало невозможным прохождение любых сообщений таймера.

Решением, довольно просто, было дать таймер обоимокна, чтобы установить скорость, с которой они будут обновлять свои цели рендеринга D3D. В сочетании с проверкой фокуса я теперь могу легко переключаться между обоими окнами, при этом в каждый момент времени выполняется только одно обновление.

Остается вопрос, является ли система таймера WinAPI лучшим выбором. Помимо моего плохого кода, люди упоминали, что сообщения таймера имеют низкий приоритет, что может сделать его плохим выбором для управления частотой кадров в долгосрочной перспективе.

РЕДАКТИРОВАТЬ: я закончил с IInspectable *Предложение 1013 * использовать цикл while в основном цикле сообщений для обработки для отправки всех сообщений, и после того, как они обработаны, я выполняю обновление кадра. Это позволяет мне постоянно обновлять все окна и контролировать частоту кадров, не рискуя при этом застревать в сообщениях окон.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...