c++ - seleccion - ¿Cómo obligo a las ventanas a no volver a dibujar nada en mi cuadro de diálogo cuando el usuario cambia el tamaño de mi cuadro de diálogo?
no me aparece el cuadro de comandos en autocad (7)
Cuando el usuario toma una esquina de una ventana de tamaño variable y luego la mueve, Windows primero mueve el contenido de la ventana y luego emite un WM_SIZE a la ventana que se está modificando.
Por lo tanto, en un diálogo donde quiero controlar el movimiento de varios controles secundarios, y quiero eliminar el parpadeo, el usuario primero ve lo que Windows OS piensa que será la ventana (porque, AFAICT, el sistema operativo usa un enfoque bitblt para moverse dentro de la ventana antes de enviar el WM_SIZE) - y solo entonces mi cuadro de diálogo maneja mover sus controles secundarios, o cambiar su tamaño, etc., después de lo cual debe obligar a las cosas a volver a pintar, lo que ahora causa parpadeo (en el menos).
Mi pregunta principal es: ¿hay alguna manera de obligar a Windows a NO hacer esta estúpida cosa bitblt? Definitivamente va a estar mal en el caso de una ventana con controles que se mueven a medida que se cambia el tamaño de la ventana, o que cambian de tamaño a medida que se cambia el tamaño de sus padres. De cualquier manera, hacer que el sistema operativo haga una pintura previa simplemente atornilla las obras.
Pensé por un tiempo que podría estar relacionado con los indicadores de clase CS_HREDRAW y CSVREDRAW. Sin embargo, la realidad es que no quiero que el sistema operativo me pida que borre la ventana. Solo quiero volver a pintar sin el sistema operativo antes de cambiar el contenido de mi ventana (es decir, quiero que la pantalla sea lo que era antes de que el usuario comenzara a cambiar el tamaño, sin ningún tipo de debilitamiento del sistema operativo). Y no quiero que el sistema operativo le diga a cada control que necesita ser rediseñado tampoco (a menos que haya sido ocultado o revelado por el cambio de tamaño).
Lo que realmente quiero:
- Para mover y cambiar el tamaño de los controles secundarios antes de que se actualice nada en la pantalla.
- Dibuje todos los controles secundarios movidos o redimensionados completamente para que aparezcan sin artefactos en su nuevo tamaño y ubicación.
- Dibuje los espacios entre los controles secundarios sin afectar a los controles secundarios.
NOTA: los pasos 2 y 3 podrían invertirse.
Las tres cosas anteriores parecen ocurrir correctamente cuando uso DeferSetWindowPos () en combinación con el recurso de diálogo marcado como WS_CLIPCHILDREN.
Me gustaría obtener un pequeño beneficio adicional si pudiera hacer lo anterior en una memoria DC, y luego solo hacer un bitblt al final del controlador WM_SIZE.
He jugado con esto por un tiempo, y no puedo escapar dos cosas:
Todavía no puedo suprimir Windows de hacer un ''bitblt predictivo''. Respuesta: Consulte a continuación una solución que anula WM_NCCALCSIZE para deshabilitar este comportamiento.
No veo cómo se puede construir un diálogo donde sus controles secundarios se dibujen en un doble buffer. Respuesta: Consulte la respuesta de John (marcada como respuesta) a continuación para saber cómo solicitar al sistema operativo Windows que duplique el búfer de su cuadro de diálogo (nota: esto no permite ninguna operación de pintura intermedia de GetDC () según los documentos).
Mi solución final (Gracias a todos los que contribuyeron, especialmente John K.):
Después de mucho sudor y lágrimas, he descubierto que la siguiente técnica funciona sin problemas, tanto en Aero como en XP o con Aero deshabilitado. Flicking no existe (1).
- Enganche el procedimiento de diálogo.
- Sustituya WM_NCCALCSIZE para forzar a Windows a validar toda el área del cliente y no bitblt nada.
- Anule WM_SIZE para hacer todos sus movimientos y cambie el tamaño usando BeginDeferWindowPos / DeferWindowPos / EndDeferWindowPos para todas las ventanas visibles.
- Asegúrese de que la ventana de diálogo tenga el estilo WS_CLIPCHILDREN.
- NO use CS_HREDRAW | CS_VREDRAW (los diálogos no, por lo que generalmente no es un problema).
El código de diseño depende de usted; es bastante fácil encontrar ejemplos en CodeGuru o CodeProject de administradores de diseño, o hacer su propio diseño.
Aquí hay algunos extractos de código que deberían ayudarte a obtener la mayor parte del camino:
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);
}
El tamaño lo realiza realmente el miembro Resize (), así:
// 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));
}
Y tal vez el último truco se puede ver en el controlador Reposition () de 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;
}
El ''truco'' es que evitamos tratar de meternos con cualquier ventana que haya sido destruida, y no tratamos de diferir un SetWindowPos contra una ventana que no está visible (ya que está documentado como "fallará").
He probado lo anterior en un proyecto real que oculta algunos controles y hace uso de diseños bastante complejos con excelente éxito. No hay ningún parpadeo (1) incluso sin Aero, incluso cuando cambia el tamaño utilizando la esquina superior izquierda de la ventana de diálogo (la mayoría de las ventanas de tamaño variable mostrarán más parpadeos y problemas cuando agarre ese identificador - IE, FireFox, etc.).
Si hay suficiente interés, podría persuadirse de editar mis hallazgos con una implementación de ejemplo real para CodeProject.com o similar. Mensajeame.
(1) Tenga en cuenta que es imposible evitar un sorteo sobre la parte superior de lo que solía estar allí. Para cada parte del diálogo que no ha cambiado, el usuario no puede ver nada (sin parpadeo alguno). Pero donde las cosas han cambiado, hay un cambio visible para el usuario: esto es imposible de evitar y es una solución al 100%.
Hay varios enfoques, pero encontré que el único que se puede usar generalmente es el doble buffering: dibujar en un búfer fuera de la pantalla, luego ajustar todo el búfer a la pantalla.
Eso viene gratis en Vista Aero y superior, por lo que su dolor puede ser de corta duración.
No estoy al tanto de una implementación general de doble buffer para Windows y controles de sistema bajo XP. Sin embargo, he aquí algunas cosas para explorar:
CMemDC de Keith Rule para almacenar en doble buffer todo lo que dibuje con GDI
WS_EX_COMPOSITED Estilo de ventana (vea la sección de comentarios, y algo aquí en )
Lo que parece funcionar:
- Use WS_CLIPCHILDREN en el cuadro de diálogo principal (puede establecerse en WM_INITDIALOG)
- Durante WM_SIZE, recorra los controles secundarios moviéndolos y redimensionándolos usando DeferSetWindowPos ().
Esto está muy cerca de ser perfecto, en mis pruebas bajo Windows 7 con Aero.
No puede evitar pintar durante el cambio de tamaño, pero puede (con cuidado) evitar el repintado, que es donde proviene el parpadeo. primero, el bitblt.
Hay dos formas de detener la cosa bitblt.
Si posee la clase de la ventana de nivel superior, simplemente regístrela con CS_HREDRAW | CS_VREDRAW
Estilos CS_HREDRAW | CS_VREDRAW
. Esto causará un cambio de tamaño de su ventana para invalidar toda el área del cliente, en lugar de tratar de adivinar qué bits no van a cambiar y bitblting.
Si no posee la clase, pero tiene la capacidad de controlar el manejo de mensajes (verdadero para la mayoría de los cuadros de diálogo). El procesamiento predeterminado de WM_NCCALCSIZE
es donde se WM_NCCALCSIZE
los estilos de clase CS_HREDRAW
y CS_VREDRAW
. El comportamiento predeterminado es devolver WVR_HREDRAW | WVR_VREDRAW
WVR_HREDRAW | WVR_VREDRAW
del procesamiento WM_NCCALCSIZE cuando la clase tiene CS_HREDRAW | CS_VREDRAW
CS_HREDRAW | CS_VREDRAW
.
Entonces, si puede interceptar WM_NCCALCSIZE, puede forzar el retorno de estos valores después de llamar a DefWindowProc para hacer el otro procesamiento normal.
Puede escuchar WM_ENTERSIZEMOVE
y WM_EXITSIZEMOVE
para saber cuándo se inicia y se detiene el cambio de tamaño de su ventana, y usarlo para desactivar o modificar temporalmente la forma en que su código de diseño y / o dibujo funciona para minimizar el parpadeo. Lo que exactamente quiere hacer para modificar este código dependerá de lo que normalmente hace su código normal en WM_SIZE
WM_PAINT
y WM_ERASEBKGND
.
Cuando pinta el fondo de su cuadro de diálogo, no debe pintar detrás de ninguna de las ventanas secundarias. asegurándose de que el diálogo tiene WS_CLIPCHILDREN resuelve esto, por lo que ya se ha manejado esto.
Cuando mueva las ventanas secundarias, asegúrese de usar BeginDeferWindowPos / EndDefwindowPos para que todo el repintado ocurra a la vez. De lo contrario, obtendrá un montón de flashes ya que cada ventana redibuja su área no cliente en cada llamada a SetWindowPos.
Para algunos controles, puede usar el mensaje WM_PRINT para hacer que el control se dibuje en un DC. Pero eso realmente no resuelve tu problema principal, que es que quieres que Windows NO dibuje nada durante el cambio de tamaño, sino que te permita hacerlo todo.
Y la respuesta es que simplemente no puede hacer lo que quiera siempre que tenga ventanas secundarias.
La forma en que terminé solucionando esto eventualmente en mi propio código es cambiar al uso de controles sin ventanas . Como no tienen ventana propia, siempre dibujan al mismo tiempo (y en el mismo DC) que su ventana principal. Esto me permite usar doble buffering para eliminar por completo el parpadeo. Incluso puedo suprimir trivialmente la pintura de los niños cuando necesito simplemente no llamar a su rutina de sorteo dentro de la rutina de sorteo de los padres.
Esta es la única forma que conozco para eliminar por completo el parpadeo y el desgarro durante las operaciones de cambio de tamaño.
Si entendí la pregunta correctamente, es exactamente la pregunta que Raymond dirigió hoy .
Si puede encontrar un lugar para conectarlo, CWnd::LockWindowUpdates()
evitará que ocurra ningún dibujo hasta después de que haya desbloqueado las actualizaciones.
Pero ten en cuenta que este es un truco, y bastante feo. Tu ventana se verá terrible durante el cambio de tamaño. Si el problema que está teniendo está parpadeando durante el cambio de tamaño, lo mejor que puede hacer es diagnosticar el parpadeo, en lugar de ocultar el parpadeo bloqueando las pinturas.
Una cosa a tener en cuenta es volver a dibujar los comandos que se llaman con demasiada frecuencia durante el cambio de tamaño. Si los controles de su ventana están llamando a RedrawWindow()
con el indicador RDW_UPDATENOW
especificado, se RDW_UPDATENOW
a pintar allí mismo. Pero puede quitar esa bandera y especificar RDW_INVALIDATE
en RDW_INVALIDATE
lugar, lo que le indica al control que invalide la ventana sin repintar. Se volverá a pintar en tiempo de inactividad, manteniendo la pantalla fresca sin espasmos.
solo hay una forma de diagnosticar eficazmente los problemas de repintado: la depuración remota.
Consigue una segunda PC. Instale MSVSMON en él. Agregue un paso de compilación posterior o un proyecto de utilidad que copie sus productos de compilación en la PC remota.
Ahora debería poder colocar puntos de interrupción en controladores WM_PAINT, manejadores WM_SIZE, etc., y realmente rastrear a través del código de diálogo a medida que realiza el tamaño y redibuja. Si descarga símbolos de los servidores de símbolos de MS, podrá ver las pilas de llamadas completas.
Algunos puntos de interrupción bien ubicados: en sus manejadores WM_PAINT, WM_ERAGEBKGND y usted debe tener una buena idea de por qué su ventana se repinta sincrónicamente temprano durante el ciclo WM_SIZE.
Hay MUCHAS ventanas en el sistema que consisten en una ventana principal con controles secundarios en capas: las ventanas del explorador se complican enormemente con listviews, paneles de vista previa de TreeView, etc. Explorer no tiene un problema de parpadeo al cambiar el tamaño, por lo que es posible obtener cambio de tamaño sin parpadeo de las ventanas principales: lo que debe hacer es tomar las impresiones, averiguar qué las causó y, bueno, asegurarse de que se eliminó la causa.