Когда пользователь захватывает угол изменяемого размера окна, а затем перемещает его, окна сначала перемещают содержимое окна вокруг, а затем выдает 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% -ным решением.