c# winforms performance gdi+ doublebuffered

c# - Winforms: SuspendLayout/ResumeLayout no es suficiente?



performance gdi+ (10)

Tengo una biblioteca de unos pocos "controles personalizados". Esencialmente tenemos nuestros propios botones, paneles de esquina redondeados y algunos cuadros de grupo con pintura personalizada. A pesar de la "matemática" en los métodos de OnPaint, los controles son bastante estándar. La mayoría de las veces, todo lo que hacemos es dibujar las esquinas redondeadas y agregar un degradado al fondo. Usamos GDI + para todo eso.

Sin embargo, estos controles están bien (y se ven muy bien de acuerdo con nuestros clientes) y, a pesar del DoubleBuffer, puede ver algunos redibujes, especialmente cuando hay 20 botones ++ (en el ejemplo) en el mismo formulario. En la carga de formularios se ven los botones dibujando ... lo cual es molesto.

Estoy bastante seguro de que nuestros botones no son lo más rápido del mundo, pero mi pregunta es: si el búfer doble está "activado", ¿no debería ocurrir todo ese redibujado en segundo plano y el subsistema de Windows debería mostrar los resultados "al instante"?

Por otro lado, si hay un bucle foreach "complejo" que creará etiquetas, las agregará a un panel (con doble búfer) y cambiará sus propiedades, si suspendemos la reproducción del panel antes del bucle y reanudamos el diseño del panel cuando el bucle es Además, ¿no deberían aparecer todos estos controles (etiquetas y botones) "casi al instante"? Esto no sucede así, puedes ver que se llena el panel.

¿Alguna idea de por qué esto no está sucediendo? Sé que es difícil de evaluar sin código de ejemplo, pero eso también es difícil de replicar. Podría hacer un video con una cámara, pero créeme, no es rápido :)


Abordaré su problema desde un ángulo de rendimiento.

bucle foreach que creará etiquetas, las agregará a un panel (doble búfer) y cambiará sus propiedades

Si ese es el orden en que se hacen las cosas, hay espacio para mejorar. Primero cree todas sus etiquetas, cambie sus propiedades y, cuando estén listas, agréguelas al panel: Panel.Controls.AddRange(Control[])

La mayoría de las veces, todo lo que hacemos es dibujar las esquinas redondeadas y agregar degradado al fondo

¿Estás haciendo lo mismo una y otra vez? ¿Cómo se generan tus gradientes? Escribir una imagen no puede ser tan lento. Una vez tuve que crear un gradiente de 1680x1050 en memoria, y fue muy rápido, demasiado rápido para el Stopwatch , por lo que dibujar un gradiente no puede ser tan difícil.

Mi consejo sería tratar de almacenar algunas cosas. Abra Paint, dibuje sus esquinas y guárdelo en el disco, o genere una imagen en memoria solo una vez. Luego cargue (y cambie el tamaño) según sea necesario. Lo mismo para el gradiente.

Incluso si diferentes botones tienen diferentes colores, pero el mismo motivo, puede crear un mapa de bits con Paint o lo que sea y cargarlo en tiempo de ejecución y multiplicar los valores de Color por otro Color.

EDITAR:

si suspendemos la reproducción del panel antes del bucle y reanudamos el diseño del panel cuando finaliza el bucle

Eso no es para lo que son SuspendLayout y ResumeLayout. Suspenden la lógica de diseño, es decir, el posicionamiento automático de los controles. Lo más relevante con FlowLayoutPanel y TableLayoutPanel.

En cuanto al doble comportamiento, no estoy seguro de que se aplique al código de sorteo personalizado (no lo he intentado). Supongo que deberías implementar el tuyo.

Doublebuffering en pocas palabras: Es muy simple, un par de líneas de código. En el evento paint, renderice en un mapa de bits en lugar de renderizarlo en el objeto Graphics , y luego dibuje ese mapa de bits en el objeto Graphics .


Además de la propiedad DoubleBuffered , también intente agregar esto al constructor de su control:

SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);

Y si eso no es suficiente (lo cual voy a arriesgarme y diré que no lo es), considere echar un vistazo a mi respuesta a esta pregunta y suspender / reanudar el redibujado del panel o el Formulario. Esto permitiría que sus operaciones de diseño se completen, luego haga todo el dibujo una vez que haya terminado.


He tenido muchos problemas similares en el pasado, y la forma en que resolví fue utilizar una suite de IU de terceros (es decir, DevExpress ) en lugar de los controles estándar de Microsoft.

Comencé a usar los controles estándar de Microsoft, pero descubrí que estaba constantemente depurando problemas causados ​​por sus controles. El problema se agrava por el hecho de que, en general, Microsoft no resuelve ninguno de los problemas identificados y hacen muy poco para proporcionar soluciones adecuadas.

Cambié a DevExpress, y no tengo más que cosas buenas que decir. El producto es sólido, proporciona un gran soporte y documentación y sí, realmente escuchan a sus clientes. Cada vez que tenía una pregunta o un problema, recibí una respuesta amistosa dentro de las 24 horas. En un par de casos, encontré un error y en ambos casos, implementaron una solución para el próximo lanzamiento de servicio.


He visto parpadear las formas de Windows malas en formularios donde los controles se referían a una fuente faltante.

Probablemente esto no sea común, pero vale la pena investigar si has probado todo lo demás.


Parece que lo que está buscando es una pantalla "compuesta", donde se dibuja toda la aplicación a la vez, casi como un gran mapa de bits. Esto es lo que sucede con las aplicaciones WPF, excepto el "chrome" que rodea a la aplicación (cosas como la barra de título, los controladores de tamaño y las barras de desplazamiento).

Tenga en cuenta que normalmente, a menos que haya ensuciado algunos de los estilos de ventana, cada control de Windows Form es responsable de pintarse. Es decir, cada control recibe un crack en los mensajes relacionados con la pintura WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc. y maneja estos mensajes de forma independiente. Lo que esto significa para usted es que el búfer doble solo se aplica al control único con el que está tratando. Para acercarse un poco a un efecto limpio y compuesto, debe preocuparse no solo de los controles personalizados que está dibujando, sino también de los controles de contenedor en los que se encuentran. Por ejemplo, si tiene un formulario que contiene un cuadro de grupo que a su vez contiene una cantidad de botones dibujados personalizados, cada uno de estos controles debería tener la propiedad DoubleBuffered establecida en Verdadero. Tenga en cuenta que esta propiedad está protegida, por lo que esto significa que puede terminar heredando para los diversos controles (solo para establecer la propiedad de doble búfer) o usar la reflexión para establecer la propiedad protegida. Además, no todos los controles de Windows Form respetan la propiedad DoubleBuffered, ya que internamente algunos de ellos son solo envoltorios alrededor de los controles "comunes" nativos.

Hay una manera de establecer una bandera compuesta si está apuntando a Windows XP (y probablemente más adelante). Existe el estilo de ventana WS_ EX_ COMPOSITED. Lo he usado antes para mezclar resultados. No funciona bien con las aplicaciones híbridas WPF / WinForm y tampoco funciona bien con el control DataGridView. Si sigue esta ruta, asegúrese de realizar muchas pruebas en diferentes máquinas porque he visto resultados extraños. Al final, abandoné el uso de este enfoque.


Tal vez primero dibuje en un búfer ''visible'' (privado) solo de control y luego renderícelo:

Bajo tu control

BufferedGraphicsContext gfxManager; BufferedGraphics gfxBuffer; Graphics gfx;

Una función para instalar gráficos.

private void InstallGFX(bool forceInstall) { if (forceInstall || gfxManager == null) { gfxManager = BufferedGraphicsManager.Current; gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height)); gfx = gfxBuffer.Graphics; } }

En su método de pintura.

protected override void OnPaint(PaintEventArgs e) { InstallGFX(false); // .. use GFX to draw gfxBuffer.Render(e.Graphics); }

En su método de redimensionamiento.

protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); InstallGFX(true); // To reallocate drawing space of new size }

El código anterior ha sido algo probado.



También hemos visto este problema.

Una forma que hemos visto para "arreglarlo" es suspender completamente el dibujo del control hasta que estemos listos para comenzar. Para lograr esto, enviamos el mensaje WM_SETREDRAW al control:

// Note that WM_SetRedraw = 0XB // Suspend drawing. UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero); ... // Resume drawing. UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);


Tuve el mismo problema con un tablelayoutpanel al cambiar los controles de usuario que quería que se mostraran.

Me deshice completamente del parpadeo creando una clase que heredó la tabla y luego habilité el doble búfer.

using System; using System.Collections.Generic; using System.Text; using System.Windows.Forms; namespace myNameSpace.Forms.UserControls { public class TableLayoutPanelNoFlicker : TableLayoutPanel { public TableLayoutPanelNoFlicker() { this.DoubleBuffered = true; } } }


Una de las cosas que debe tener en cuenta es si ha configurado BackColor = Transparent en cualquiera de los controles secundarios de sus paneles. BackColor = Transparent degradará significativamente el rendimiento de la representación, especialmente si los paneles principales utilizan gradientes.

Windows Forms no usa transparencia real, sino que usa una falsa. Cada llamada de control de control secundario genera una llamada de dibujo en el elemento principal para que el elemento principal pueda pintar su fondo sobre el que el elemento secundario pinta su contenido para que se vea transparente.

Por lo tanto, si tiene 50 controles secundarios que generarán 50 llamadas de pintura adicionales en el control principal para la pintura de fondo. Y como los gradientes son generalmente más lentos, verá una degradación del rendimiento.

Espero que esto ayude.