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

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

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

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

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

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

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

Примечание: шаги 2 и 3 могут быть отменены.

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

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

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

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

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


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

после Большого пота и слез я обнаружил, что следующий метод работает безупречно, как в Aero, так и в XP или с Aero disabled. Щелчка не существует(1).

  1. крюк диалог тез.Докл.
  2. переопределить WM_NCCALCSIZE для принудительного Windows для проверки всей клиентской области, а не bitblt ничего.
  3. переопределить WM_SIZE, чтобы сделать все ваши движения и изменения размера с помощью BeginDeferWindowPos / DeferWindowPos / EndDeferWindowPos для всех видимых окон.
  4. убедитесь,что диалоговое окно имеет стиль WS_CLIPCHILDREN.
  5. не используйте 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/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);
}

изменение размера действительно выполняется членом 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));
}

и, возможно, последний сложный бит можно увидеть в обработчике Reposition() 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;
}

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

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

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

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

7 ответов


вы не можете предотвратить покраску во время изменения размера, но вы можете (с осторожностью) предотвратить перекраску вот откуда приходит мерцание. во-первых, bitblt.

есть два способа остановить bitblt вещь.

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

если вы не владеете классом, но имеете возможность управлять обработкой сообщений (true для большинства диалоговых окон). Обработка по умолчанию WM_NCCALCSIZE где стили класс CS_HREDRAW и CS_VREDRAW обрабатываются, поведение по умолчанию-return 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 и позвонить.


Если я правильно понял вопрос, это именно вопрос Раймонд выступил сегодня.


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

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

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

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


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

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

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


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

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

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

CMemDC Кейта Рула для двойная буферизация всего, что вы рисуете самостоятельно с помощью GDI
ws_ex_composited стиль окна (см. раздел Примечания, и что-то здесь, на stackoverflow)


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

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

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

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

в системе есть много окон, которые состоят из родительского окна с многоуровневыми дочерними элементами управления-окна Проводника массивно сложны с listviews, treeviews preview panels и т. д. Explorer не имеет проблемы мерцания при изменении размера, таким образом, можно получить мерцание без изменения размера родительских окон :- что вам нужно сделать, это поймать перекраски, выяснить, что их вызвало, и, ну, убедиться, что причина удалена.


что, кажется, работает:

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

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