Como forço o Windows a não redesenhar nada na minha caixa de diálogo quando o usuário está redimensionando minha caixa de diálogo?

Quando o usuário pega um canto de uma janela redimensionável e depois a move, as janelas primeiro movem o conteúdo da janela e, em seguida, emitem um WM_SIZE para a janela que está sendo redimensionada.

Portanto, em uma caixa de diálogo em que desejo controlar o movimento de vários controles filho e eliminar os tremores, o usuário primeiro vê como o sistema operacional Windows pensa que será a janela (porque, AFAICT, o sistema operacional usa uma abordagem bitblt para mover coisas dentro da janela antes de enviar o WM_SIZE) - e apenasentão minha caixa de diálogo consegue manipular a movimentação de seus controles filhos, ou redimensioná-los, etc., após o que deve forçar as redesenhas a pintar, o que agora causa cintilação (no mínimo).

Minha pergunta principal é:Existe uma maneira de forçar o Windows a não fazer essa coisa estúpida de bitblt? Definitivamente, estará errado no caso de uma janela com controles que se movem à medida que a janela é redimensionada ou que se redimensionam à medida que o pai é redimensionado. De qualquer forma, ter o sistema operacional fazendo uma pré-pintura apenas estraga os trabalhos.

Pensei por um tempo que isso poderia estar relacionado aos sinalizadores de classe CS_HREDRAW e CSVREDRAW. No entanto, a realidade é que eu não quero que o sistema operacional me peça para apagar a janela - eu só quero fazer as redesenhas sem que o sistema operacional altere primeiro o conteúdo da minha janela (ou seja, eu quero que a tela seja como era) antes que o usuário começasse a redimensionar - sem nenhum bitblit'ing do sistema operacional). E não quero que o sistema operacional diga a todos os controles que ele também precisa ser redesenhado (a menos que tenha sido realmente um obscurecido ou revelado pelo redimensionamento).

O que eu realmente quero:

Para mover e redimensionar controles filhoantes qualquer coisa é atualizada na tela.Desenhe todos os controles filho movidos ou redimensionados completamente para que eles apareçam sem artefatos em seu novo tamanho e local.Desenhe os espaços entre os controles filho sem afetar o controle deles.

NOTA: As etapas 2 e 3 podem ser revertidas.

As três coisas acima parecem acontecer corretamente quando eu uso DeferSetWindowPos () em combinação com o recurso de diálogo marcado como WS_CLIPCHILDREN.

Eu receberia um pequeno benefício adicional se pudesse fazer o acima em um controlador de domínio da memória e, em seguida, executar apenas um único bitblt no final do manipulador WM_SIZE.

Já brinco com isso há um tempo e não consigo escapar de duas coisas:

Ainda não consigo suprimir o Windows de fazer um 'bitblt preditivo'.Resposta: Veja abaixo uma solução que substitui WM_NCCALCSIZE para desativar esse comportamento.

Não consigo ver como é possível criar uma caixa de diálogo onde seus controles filho são desenhados para um buffer duplo.Resposta: Consulte a resposta de John (marcada como resposta) abaixo para saber como solicitar ao sistema operacional Windows um buffer duplo da sua caixa de diálogo (nota: isso não permite qualquer GetDC () entre as operações de pintura, de acordo com os documentos).

Minha solução final (obrigado a todos que contribuíram, especialmente John K.):

Depois de muito suor e lágrimas, descobri que a técnica a seguir funciona perfeitamente, no Aero e no XP ou com o Aero desativado. O movimento é inexistente (1).

Conecte o processo de diálogo.Substitua WM_NCCALCSIZE para forçar o Windows a validar toda a área do cliente e não bitblt nada.Substitua WM_SIZE para executar todos os seus movimentos e redimensionamentos usando BeginDeferWindowPos / DeferWindowPos / EndDeferWindowPos para todas as janelas visíveis.Verifique se a janela de diálogo possui o estilo WS_CLIPCHILDREN.NÃO use CS_HREDRAW | CS_VREDRAW (as caixas de diálogo não, geralmente não são um problema).

O código do layout é com você - é fácil encontrar exemplos no CodeGuru ou no CodeProject de gerenciadores de layout ou criar o seu próprio.

Aqui estão alguns trechos de código que devem ajudá-lo a maior parte do caminho:

LRESULT ResizeManager::WinProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case WM_ENTERSIZEMOVE:
        m_bResizeOrMove = true;
        break;

    case WM_NCCALCSIZE:
        // The WM_NCCALCSIZE idea was given to me by John Knoeller: 
        // see: http://stackoverflow.com/questions/2165759/how-do-i-force-windows-not-to-redraw-anything-in-my-dialog-when-the-user-is-resiz
        // 
        // The default implementation is to simply return zero (0).
        //
        // The MSDN docs indicate that this causes Windows to automatically move all of the child controls to follow the client's origin
        // and experience shows that it bitblts the window's contents before we get a WM_SIZE.
        // Hence, our child controls have been moved, everything has been painted at its new position, then we get a WM_SIZE.
        //
        // Instead, we calculate the correct client rect for our new size or position, and simply tell windows to preserve this (don't repaint it)
        // and then we execute a new layout of our child controls during the WM_SIZE handler, using DeferWindowPos to ensure that everything
        // is moved, sized, and drawn in one go, minimizing any potential flicker (it has to be drawn once, over the top at its new layout, at a minimum).
        //
        // It is important to note that we must move all controls.  We short-circuit the normal Windows logic that moves our child controls for us.
        //
        // Other notes:
        //  Simply zeroing out the source and destination client rectangles (rgrc[1] and rgrc[2]) simply causes Windows 
        //  to invalidate the entire client area, exacerbating the flicker problem.
        //
        //  If we return anything but zero (0), we absolutely must have set up rgrc[0] to be the correct client rect for the new size / location
        //  otherwise Windows sees our client rect as being equal to our proposed window rect, and from that point forward we're missing our non-client frame

        // only override this if we're handling a resize or move (I am currently unaware of how to distinguish between them)
        // though it may be adequate to test for wparam != 0, as we are
        if (bool bCalcValidRects = wparam && m_bResizeOrMove)
        {
            NCCALCSIZE_PARAMS * nccs_params = (NCCALCSIZE_PARAMS *)lparam;

            // ask the base implementation to compute the client coordinates from the window coordinates (destination rect)
            m_ResizeHook.BaseProc(hwnd, msg, FALSE, (LPARAM)&nccs_params->rgrc[0]);

            // make the source & target the same (don't bitblt anything)
            // NOTE: we need the target to be the entire new client rectangle, because we want windows to perceive it as being valid (not in need of painting)
            nccs_params->rgrc[1] = nccs_params->rgrc[2];

            // we need to ensure that we tell windows to preserve the client area we specified
            // if I read the docs correctly, then no bitblt should occur (at the very least, its a benign bitblt since it is from/to the same place)
            return WVR_ALIGNLEFT|WVR_ALIGNTOP;
        }
        break;

    case WM_SIZE:
        ASSERT(m_bResizeOrMove);
        Resize(hwnd, LOWORD(lparam), HIWORD(lparam));
        break;

    case WM_EXITSIZEMOVE:
        m_bResizeOrMove = false;
        break;
    }

    return m_ResizeHook.BaseProc(hwnd, msg, wparam, lparam);
}

O redimensionamento é realmente feito pelo membro Resize (), assim:

// execute the resizing of all controls
void ResizeManager::Resize(HWND hwnd, long cx, long cy)
{
    // defer the moves & resizes for all visible controls
    HDWP hdwp = BeginDeferWindowPos(m_resizables.size());
    ASSERT(hdwp);

    // reposition everything without doing any drawing!
    for (ResizeAgentVector::const_iterator it = m_resizables.begin(), end = m_resizables.end(); it != end; ++it)
        VERIFY(hdwp == it->Reposition(hdwp, cx, cy));

    // now, do all of the moves & resizes at once
    VERIFY(EndDeferWindowPos(hdwp));
}

E talvez a parte mais complicada possa ser vista no manipulador Reposition () do ResizeAgent:

HDWP ResizeManager::ResizeAgent::Reposition(HDWP hdwp, long cx, long cy) const
{
    // can't very well move things that no longer exist
    if (!IsWindow(hwndControl))
        return hdwp;

    // calculate our new rect
    const long left   = IsFloatLeft()   ? cx - offset.left    : offset.left;
    const long right  = IsFloatRight()  ? cx - offset.right   : offset.right;
    const long top    = IsFloatTop()    ? cy - offset.top     : offset.top;
    const long bottom = IsFloatBottom() ? cy - offset.bottom  : offset.bottom;

    // compute height & width
    const long width = right - left;
    const long height = bottom - top;

    // we can defer it only if it is visible
    if (IsWindowVisible(hwndControl))
        return ::DeferWindowPos(hdwp, hwndControl, NULL, left, top, width, height, SWP_NOZORDER|SWP_NOACTIVATE);

    // do it immediately for an invisible window
    MoveWindow(hwndControl, left, top, width, height, FALSE);

    // indicate that the defer operation should still be valid
    return hdwp;
}

O 'complicado' é que evitamos tentar mexer com as janelas que foram destruídas e não tentamos adiar um SetWindowPos contra uma janela que não é visível (pois isso está documentado como "falhará").

Testei o que foi dito acima em um projeto real que oculta alguns controles e faz uso de layouts bastante complexos com excelente sucesso. Não há oscilação zero (1) mesmo sem o Aero, mesmo quando você redimensiona usando o canto superior esquerdo da janela de diálogo (a maioria das janelas redimensionáveis mostra mais oscilações e problemas quando você pega a alça - IE, FireFox etc.).

Se houver interesse suficiente, eu poderia ser persuadido a editar minhas descobertas com um exemplo real de implementação para CodeProject.com ou algo semelhante. Me mande uma mensagem.

(1) Observe que é impossível evitar um empate por cima do que costumava estar lá. Para todas as partes da caixa de diálogo que não foram alteradas, o usuário não pode ver nada (sem piscar). Mas onde as coisas mudaram, há uma alteração visível para o usuário - isso é impossível de evitar e é uma solução 100%.

questionAnswers(8)

yourAnswerToTheQuestion