Как заставить окна НЕ перерисовывать что-либо в моем диалоговом окне, когда пользователь меняет размер моего диалога?

Когда пользователь захватывает угол изменяемого размера окна, а затем перемещает его, окна сначала перемещают содержимое окна вокруг, а затем выдает WM_SIZE в изменяемое окно.

Таким образом, в диалоговом окне, где я хочу контролировать перемещение различных дочерних элементов управления, и я хочу устранить мерцание, пользователь сначала видит, что ОС Windows считает, что окно будет выглядеть (потому что, AFAICT, ОС использует бит битт подход к перемещению вещей внутри окна перед отправкой WM_SIZE) - и только , а затем мой диалог позволяет обрабатывать перемещение своих дочерних элементов управления или изменять их размер и т.д., после чего он должен заставить вещи repaint, который теперь вызывает мерцание (по крайней мере).

Мой главный вопрос: Есть ли способ заставить окна НЕ делать эту глупую вещь битбт?. Это определенно будет ошибкой в ​​случае окна с элементами управления, которые перемещаются по мере того, как окно изменение размера или изменение размеров, поскольку их родительский размер изменен. В любом случае, если ОС делает предварительную краску, просто закручивает работы.

Я подумал, что это может быть связано с флагов класса CS_HREDRAW и CSVREDRAW. Однако реальность заключается в том, что я не хочу, чтобы ОС попросила меня стереть окно - я просто хочу сделать перекраску самостоятельно без ОС, сначала меняя содержимое моего окна (т.е. Я хочу, чтобы дисплей был тем, чем он был прежде чем пользователь начнет изменять размер - без каких-либо битбитов из ОС). И я не хочу, чтобы ОС рассказывала каждому элементу управления, что его нужно перерисовать (если только это не было тем, что было на самом деле скрыто или обнаружено изменением размера.

Что я действительно хочу:

  • Чтобы переместить и изменить размер дочерних элементов управления до, все будет обновляться на экране.
  • Нарисуйте все перемещенные или измененные размеры дочерних элементов управления полностью, чтобы они отображались без артефактов в их новом размере и местоположении.
  • Нарисуйте пробелы между дочерними элементами управления, не затрагивая сами дочерние элементы управления.

ПРИМЕЧАНИЕ. Шаги 2 и 3 могут быть отменены.

Приведенные выше три вещи выглядят правильно, когда я использую DeferSetWindowPos() в сочетании с ресурсом диалога, отмеченным как WS_CLIPCHILDREN.

Я получил бы дополнительное небольшое преимущество, если бы мог сделать это в памяти DC, а затем сделать только один бит битт в конце обработчика WM_SIZE.

Я играл с этим какое-то время, и я не могу избежать двух вещей:

  • Я все еще не могу запретить Windows делать "предсказательный битбитт". Ответ: см. ниже решение, которое переопределяет WM_NCCALCSIZE, чтобы отключить это поведение.

  • Я не вижу, как можно построить диалог, в котором его дочерние элементы управления обращаются к двойному буферу. Ответ: см. ответ Джона (помечен как ответ) ниже, чтобы узнать, как Windows OS удваивает буфер вашего диалога (обратите внимание: это запрещает любые операции с графическими операциями GetDC() в соответствии с документами).


Мое окончательное решение (Спасибо всем, кто внес свой вклад, особенно Джон К.):

После долгих пота и слез, я обнаружил, что следующая техника работает безупречно, как в Aero, так и в XP или с отключенным Aero. Flicking не существует (1).

  • Подключить диалог proc.
  • Переопределить WM_NCCALCSIZE, чтобы заставить Windows проверять всю клиентскую область, а не битовую запись.
  • Переопределите WM_SIZE, чтобы выполнить все ваши перемещения и изменения размера, используя BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos для всех видимых окон.
  • Убедитесь, что диалоговое окно имеет стиль WS_CLIPCHILDREN.
  • НЕ используйте CS_HREDRAW | CS_VREDRAW (диалогов нет, обычно это не проблема).

Код макета зависит от вас - его достаточно легко найти примеры на CodeGuru или CodeProject менеджеров макетов или катиться самостоятельно.

Вот некоторые фрагменты кода, которые должны вам больше всего помочь:

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/info/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 origin
        // and experience shows that it bitblts the window 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);
}

Изменение размера действительно выполняется с помощью элемента Resize(), например:

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

И, возможно, последний сложный бит можно увидеть в обработчике ResizeAgent Reposition():

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

"Уловка" заключается в том, что мы избегаем попытки запутаться с любыми разрушенными окнами, и мы не пытаемся отложить SetWindowPos против окна, которое не видно (поскольку это задокументировано как "сбой".

Я тестировал выше в реальном проекте, который скрывает некоторые элементы управления и использует довольно сложные макеты с отличным успехом. Существует нулевое мерцание (1) даже без Aero, даже если вы изменяете размер в верхнем левом углу диалогового окна (большинство изменяемых размеров окон будут отображаться наиболее мерцающие и проблемы, когда вы захватите этот дескриптор - IE, FireFox и т.д.).

Если есть достаточный интерес, меня можно было бы убедить изменить мои выводы с реальной реализацией примера для CodeProject.com или где-то аналогичным. Сообщите мне.

(1) Обратите внимание, что невозможно избежать одной ничьей поверх всего, что там было. Для каждой части диалогового окна, который не изменился, пользователь не видит ничего (не мерцает вообще). Но там, где ситуация изменилась, для пользователя есть видимые изменения - этого невозможно избежать и является 100% -ным решением.

Ответы

Ответ 1

Вы не можете предотвратить рисование при изменении размера, но вы можете (с осторожностью) предотвратить перекраску, из которой происходит фликер. сначала битбит.

Есть два способа остановить битбитт.

Если вы владеете классом окна верхнего уровня, просто зарегистрируйте его с помощью стилей CS_HREDRAW | CS_VREDRAW. Это приведет к изменению размера вашего окна, чтобы аннулировать всю клиентскую область, вместо того, чтобы пытаться угадать, какие биты не будут меняться, и битовая обработка.

Если вы не владеете классом, но имеете возможность управлять обработкой сообщений (true для большинства диалоговых окон). Обработка по умолчанию WM_NCCALCSIZE - это то, где обрабатываются стили класса CS_HREDRAW и CS_VREDRAW. Поведение по умолчанию заключается в возврате WVR_HREDRAW | WVR_VREDRAW из обработки WM_NCCALCSIZE, когда класс имеет CS_HREDRAW | CS_VREDRAW.

Итак, если вы можете перехватить WM_NCCALCSIZE, вы можете принудительно вернуть эти значения после вызова DefWindowProc для выполнения другой нормальной обработки.

Вы можете прослушивать WM_ENTERSIZEMOVE и WM_EXITSIZEMOVE, чтобы знать, когда начинается и прекращается изменение размера вашего окна, и используйте это, чтобы временно отключить или изменить способ работы чертежа и/или макета, чтобы минимизировать мигание. То, что вы хотите сделать, чтобы изменить этот код, будет зависеть от обычного нормального кода в WM_SIZE WM_PAINT и WM_ERASEBKGND.

Когда вы рисуете фон своего диалогового окна, вам не нужно рисовать позади любого из дочерних окон. убедитесь, что в диалоговом окне WS_CLIPCHILDREN решает это, поэтому вы уже это обработали.

Когда вы перемещаете дочерние окна, убедитесь, что вы используете BeginDeferWindowPos/EndDefwindowPos, чтобы вся перекраска происходила сразу. В противном случае вы получите кучу мигания, так как каждое окно перерисовывает свою неклиентскую область при каждом вызове SetWindowPos.

Ответ 3

Для некоторых элементов управления вы можете использовать сообщение WM_PRINT, чтобы сделать контрольную ничью в DC. Но это на самом деле не решает вашу основную проблему, а именно, что вы хотите, чтобы Windows НЕ рисовала что-либо во время изменения размера, но позволяла вам делать все это.

И ответ заключается в том, что вы просто не можете делать то, что хотите, если у вас есть дочерние окна.

То, как я в конечном итоге решал это в своем собственном коде, - это переключиться на использование Windowless Controls. Поскольку у них нет собственного окна, они всегда рисуют в одно и то же время (и в том же DC), что и их родительское окно. Это позволяет мне использовать простую двойную буферизацию, чтобы полностью удалить мерцание. Я даже могу тривиально подавить роспись детей, когда мне нужно просто не называть свою рутину рутины внутри родительской рутины.

Это единственный способ, которым я знаю, полностью избавиться от мерцания и разрыва во время операций изменения размера.

Ответ 4

Если вы можете найти место для его подключения, CWnd::LockWindowUpdates() предотвратит появление какого-либо чертежа, пока вы не разблокируете обновления.

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

Одна вещь, которую нужно искать, - это команды redraw, которые вызывается слишком часто во время изменения размера. Если вы управляете окнами, RedrawWindow() с указанным флагом RDW_UPDATENOW, он будет перерисовываться тогда и там. Но вы можете вычеркнуть этот флаг и указать вместо него RDW_INVALIDATE, который сообщает элементу управления о недействительности окна без перекраски. Он будет перерисовываться на простое время, сохраняя дисплей свежим, без размытия.

Ответ 5

Существуют различные подходы, но я нашел, что единственное, что можно использовать, как правило, - это двойная буферизация: рисовать в буфер вне экрана, а затем заливать весь буфер на экран.

Это бесплатно в Vista Aero и выше, поэтому ваша боль может быть недолговечной.

Мне не известно об общей реализации двойной буферизации для Windows и системных элементов управления под XP. Однако, вот некоторые вещи для изучения:

Keith Rule CMemDC для двойной буферизации всего, что вы нарисуете с помощью GDI
WS_EX_COMPOSITED Стиль окна (см. раздел замечаний и что-то fooobar.com/questions/172589/...)

Ответ 6

существует только один способ эффективно диагностировать проблемы перекраски - удаленная отладка.

Получите второй компьютер. Установите на него MSVSMON. Добавьте пост-проект или проект утилиты, который копирует ваши продукты сборки на удаленный компьютер.

Теперь вы можете разместить точки останова в обработчиках WM_PAINT, обработчиках WM_SIZE и т.д. и фактически прослеживать свой код диалога, поскольку он выполняет размер и перерисовывает. Если вы загружаете символы с серверов символов MS, вы сможете увидеть полные стеки вызовов.

Некоторые хорошо размещенные точки останова - в ваших обработчиках WM_PAINT, WM_ERAGEBKGND, и у вас должно быть хорошее представление о том, почему ваше окно синхронно перерисовывается в начале цикла WM_SIZE.

В системе есть много окон, которые состоят из родительского окна со слоистыми дочерними элементами управления - окна проводника очень сложны с помощью списков просмотра, панелей предварительного просмотра эскизов и т.д. Проводник не имеет проблемы с мерцанием при изменении размера, поэтому он похож на можно получить изменение размера родительских окон без мерцания: что вам нужно сделать, это поймать repaints, выяснить, что их вызвало, и, ну, убедитесь, что причина удалена.

Ответ 7

Что работает:

  • Используйте WS_CLIPCHILDREN в родительском диалоге (можно установить в WM_INITDIALOG)
  • Во время WM_SIZE цикл через дочерние элементы управления перемещает и изменяет размер их с помощью DeferSetWindowPos().

Это очень близко к совершенству, в моем тестировании под Windows 7 с Aero.