Когда пользователь захватывает угол окна с изменяемым размером, а затем перемещает его, Windows сначала перемещает содержимое окна, а затем выдает WM_SIZE для изменяемого размера окна.
Таким образом, в диалогегде я хочу управлять движением различных дочерних элементов управления и хочу устранить мерцание, пользователь сначала видит, как ОС Windows думает, что окно будет выглядеть (потому что, AFAICT, ОС использует подход bitblt для перемещения объектов внутри окнаперед отправкой WM_SIZE) - и только , а затем мой диалог обрабатывает перемещение своих дочерних элементов управления или изменяет их размер и т. д., после чего он должен заставить объекты перерисовываться, что теперь вызывает мерцание (припо крайней мере).
Мой главный вопрос: Есть ли способ заставить окна НЕ делать эту глупую штуку с бит-битом? Это определенно будет неправильно в случае окна с элементами управленияэто изменение размера окна или изменение размера самих себя при изменении размера родительского элемента.В любом случае, если операционная система выполняет предварительную покраску, это просто приводит в порядок.
Некоторое время я думал, что это может быть связано с флагами классов CS_HREDRAW и CSVREDRAW.Однако реальность такова, что я не хочу, чтобы ОС попросила меня стереть окно - я просто хочу перерисовать себя без ОС, предварительно изменив содержимое моего окна (т.е. я хочу, чтобы дисплей был таким, какой он былдо того, как пользователь начал изменять размер - без каких-либо побитовых бликов от ОС).И я не хочу, чтобы ОС сообщала каждому элементу управления о том, что он также должен быть перерисован (если только это не был тот, который был фактически скрыт или обнаружен изменением размера.
Что я действительно хочу:
- Чтобы переместить и изменить размеры дочерних элементов управления до что-либо будет обновлено на экране.
- Полностью отобразить все перемещенные или измененные размеры дочерних элементов управления, чтобы ониотображаются без артефактов в их новом размере и расположении.
- Нарисуйте пробелы между дочерними элементами управления, не затрагивая сами дочерние элементы управления.
ПРИМЕЧАНИЕ. Шаги 2 и 3 можно изменить на противоположные.
Вышеупомянутые три вещи, кажется, происходят правильно, когда я использую DeferSetWindowPos () в сочетании с ресурсом диалога, помеченным как WS_CLIPCHILDREN.
Я бы получил дополнительное небольшое преимущество, если бы я мог сделать выше, чтобыDC памяти, а затем только один бит в конце обработчика WM_SIZE.
Я играл с этим некоторое время, и я не могу сбежатьДве вещи:
Я все еще не могу запретить Windows выполнять «предсказательный битовый разряд». Ответ: См. Ниже решение, которое переопределяет WM_NCCALCSIZE, чтобы отключить это поведение.
Я не вижу, как можно построить диалог, в котором его дочерние элементы управления растягиваются до двойногобуфер. Ответ: См. Ответ Джона (помеченный как ответ) ниже о том, как попросить ОС Windows дважды буферизовать ваш диалог (примечание: это запрещает любые промежуточные операции рисования GetDC (), в соответствии с документами).
Мое окончательное решение (Спасибо всем, кто помог, особенно Джон К.):
После долгих потов и слез я обнаружил, что следующая техникаработает без нареканий, как в Aero, так и в XP или с отключенным Aero.Flicking не существует (1).
- Подключите процедуру диалога.
- Переопределите 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/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));
}
И, возможно, последний хитрый бит можно увидеть в обработчике ResizeAgent's 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 и т. 1079 *
Если будет достаточно интереса, меня могут убедить отредактировать мои выводы с помощью реального примера реализации для CodeProject.com или где-то подобного. Напишите мне.
(1) Обратите внимание, что невозможно избежать одной ничьи поверх того, что было раньше. Для каждой части диалогового окна, которая не изменилась, пользователь ничего не видит (никакого мерцания). Но там, где все изменилось, пользователю видны изменения - этого избежать невозможно, и это 100% решение.