Horrible rendimiento de redibujado del DataGridView en una de mis dos pantallas

De hecho, he resuelto esto, pero lo estoy publicando para la posteridad.

Me encontré con un problema muy extraño con DataGridView en mi sistema de doble monitor. El problema se manifiesta como un repinte EXTREMADAMENTE lento del control ( como 30 segundos para un repintado completo ), pero solo cuando está en una de mis pantallas. Cuando en el otro, la velocidad de repintado está bien.

Tengo una Nvidia 8800 GT con los últimos controladores no beta (175. algo). ¿Es un error del controlador? Dejaré eso en el air, ya que tengo que vivir con esta configuración particular. (No sucede en las tarjetas ATI, sin embargo …)

La velocidad de la pintura no tiene nada que ver con el contenido de la celda, y el dibujo personalizado no mejora en absoluto el rendimiento, incluso cuando solo se pinta un rectángulo sólido.

Luego descubrí que colocar ElementHost (del espacio de nombres System.Windows.Forms.Integration) en el formulario corrige el problema. No tiene que ser molestado; solo tiene que ser un elemento secundario del formulario en el que DataGridView también está activado. Se puede cambiar el tamaño a (0, 0) siempre que la propiedad Visible sea ​​verdadera.

No deseo agregar explícitamente la dependencia .NET 3 / 3.5 a mi aplicación; Realizo un método para crear este control en tiempo de ejecución (si puede) utilizando la reflexión. Funciona, y al menos falla con elegancia en máquinas que no tienen la biblioteca requerida; simplemente vuelve a ser lenta.

Este método también me permite aplicar para corregir mientras la aplicación se está ejecutando, lo que facilita ver qué cambian las bibliotecas de WPF en mi formulario (usando Spy ++).

Después de una gran cantidad de bashs de prueba y error, noté que habilitar el doble buffer en el control mismo (en lugar de solo el formulario) corrige el problema.


Entonces, solo necesita crear una clase personalizada basada en DataGridView para que pueda habilitar su DoubleBuffering. ¡Eso es!

class CustomDataGridView: DataGridView { public CustomDataGridView() { DoubleBuffered = true; } } 

Siempre y cuando todas mis instancias de la grilla estén usando esta versión personalizada, todo está bien. Si alguna vez me encuentro con una situación causada por esto en la que no puedo usar la solución de subclase (si no tengo el código), supongo que podría intentar inyectar ese control en el formulario 🙂 ( aunque yo ‘ Será más probable que intente usar el reflection para forzar la propiedad DoubleBuffered desde el exterior para evitar nuevamente la dependencia .

Es triste que una cosa tan trivialmente simple comiera tanto de mi tiempo …

Solo necesita crear una clase personalizada basada en DataGridView para poder habilitar su DoubleBuffering. ¡Eso es!

 class CustomDataGridView: DataGridView { public CustomDataGridView() { DoubleBuffered = true; } } 

Siempre y cuando todas mis instancias de la grilla estén usando esta versión personalizada, todo está bien. Si alguna vez me encuentro con una situación causada por esto en la que no puedo usar la solución de subclase (si no tengo el código), supongo que podría intentar inyectar ese control en el formulario 🙂 (aunque yo ‘ Será más probable que intente usar el reflection para forzar la propiedad DoubleBuffered desde el exterior para evitar nuevamente la dependencia.

Es triste que una cosa tan trivialmente simple comiera tanto de mi tiempo …

Nota: Hacer que la respuesta sea una respuesta para que la pregunta se pueda marcar como respondida

Aquí hay un código que establece la propiedad usando reflexión, sin subclases como sugiere Benoit.

 typeof(DataGridView).InvokeMember( "DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, myDataGridViewObject, new object[] { true }); 

Para las personas que buscan cómo hacerlo en VB.NET, aquí está el código:

 DataGridView1.GetType.InvokeMember("DoubleBuffered", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.SetProperty, Nothing, DataGridView1, New Object() {True}) 

Agregando a las publicaciones anteriores, para las aplicaciones de Windows Forms esto es lo que uso para los componentes de DataGridView para que sean rápidos. El código para la clase DrawingControl está debajo.

 DrawingControl.SetDoubleBuffered(control) DrawingControl.SuspendDrawing(control) DrawingControl.ResumeDrawing(control) 

Llame a DrawingControl.SetDoubleBuffered (control) después de InitializeComponent () en el constructor.

Llame a DrawingControl.SuspendDrawing (control) antes de realizar actualizaciones de big data.

Llame a DrawingControl.ResumeDrawing (control) después de hacer grandes actualizaciones de datos.

Estos últimos 2 se realizan mejor con un bloque try / finally. (o incluso mejor reescriba la clase como IDisposable y llame a SuspendDrawing() en el constructor y a ResumeDrawing() en Dispose() .

 using System.Runtime.InteropServices; public static class DrawingControl { [DllImport("user32.dll")] public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam); private const int WM_SETREDRAW = 11; ///  /// Some controls, such as the DataGridView, do not allow setting the DoubleBuffered property. /// It is set as a protected property. This method is a work-around to allow setting it. /// Call this in the constructor just after InitializeComponent(). ///  /// The Control on which to set DoubleBuffered to true. public static void SetDoubleBuffered(Control control) { // if not remote desktop session then enable double-buffering optimization if (!System.Windows.Forms.SystemInformation.TerminalServerSession) { // set instance non-public property with name "DoubleBuffered" to true typeof(Control).InvokeMember("DoubleBuffered", System.Reflection.BindingFlags.SetProperty | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic, null, control, new object[] { true }); } } ///  /// Suspend drawing updates for the specified control. After the control has been updated /// call DrawingControl.ResumeDrawing(Control control). ///  /// The control to suspend draw updates on. public static void SuspendDrawing(Control control) { SendMessage(control.Handle, WM_SETREDRAW, false, 0); } ///  /// Resume drawing updates for the specified control. ///  /// The control to resume draw updates on. public static void ResumeDrawing(Control control) { SendMessage(control.Handle, WM_SETREDRAW, true, 0); control.Refresh(); } } 

La respuesta a esto funcionó para mí también. Pensé que agregaría un refinamiento que creo que debería ser una práctica estándar para cualquiera que implemente la solución.

La solución funciona bien, excepto cuando la IU se ejecuta como una sesión de cliente en el escritorio remoto, especialmente cuando el ancho de banda de red disponible es bajo. En tal caso, el rendimiento puede empeorar mediante el uso de doble buffering. Por lo tanto, sugiero lo siguiente como una respuesta más completa:

 class CustomDataGridView: DataGridView { public CustomDataGridView() { // if not remote desktop session then enable double-buffering optimization if (!System.Windows.Forms.SystemInformation.TerminalServerSession) DoubleBuffered = true; } } 

Para obtener más información, consulte Detectar conexión de escritorio remoto

Encontré una solución al problema. Vaya a la pestaña de solución de problemas en las propiedades de visualización avanzadas y verifique el control deslizante de aceleración de hardware. Cuando obtuve la PC de mi nueva empresa de TI, estaba configurada en un tick completo y no tuve ningún problema con datagrids. Una vez que actualicé el controlador de la tarjeta de video y lo puse por completo, la pintura de los controles de la cuadrícula de datos se volvió muy lenta. Así que lo reinicié de vuelta a donde estaba y el problema desapareció.

Espero que este truco funcione para ti también.

Solo para agregar lo que hicimos para solucionar este problema: Actualizamos a los últimos controladores de Nvidia que resolvieron el problema. Ningún código tuvo que ser reescrito.

Para completar, la tarjeta era una Nvidia Quadro NVS 290 con controladores de marzo de 2008 (v. 169). La actualización a la última versión (v. 182 de febrero de 2009) mejoró significativamente los eventos de pintura para todos mis controles, especialmente para DataGridView.

Este problema no se vio en ninguna tarjeta ATI (donde ocurre el desarrollo).

¡Mejor!:

 Private Declare Function SendMessage Lib "user32" _ Alias "SendMessageA" _ (ByVal hWnd As Integer, ByVal wMsg As Integer, _ ByVal wParam As Integer, ByRef lParam As Object) _ As Integer Const WM_SETREDRAW As Integer = &HB Public Sub SuspendControl(this As Control) SendMessage(this.Handle, WM_SETREDRAW, 0, 0) End Sub Public Sub ResumeControl(this As Control) RedrawControl(this, True) End Sub Public Sub RedrawControl(this As Control, refresh As Boolean) SendMessage(this.Handle, WM_SETREDRAW, 1, 0) If refresh Then this.Refresh() End If End Sub 

Hemos experimentado un problema similar con .NET 3.0 y DataGridView en un sistema de monitor dual.

Nuestra aplicación mostraría la cuadrícula con un fondo gris, indicando que las celdas no podrían ser cambiadas. Al seleccionar un botón “cambiar configuraciones”, el progtwig cambiaría el color de fondo de las celdas en blanco para indicar al usuario que el texto de la celda podría cambiarse. Un botón “cancelar” cambiaría el color de fondo de las celdas antes mencionadas a gris.

A medida que cambiaba el color de fondo, se producía un parpadeo, una breve impresión de una cuadrícula de tamaño predeterminado con el mismo número de filas y columnas. Este problema solo ocurriría en el monitor primario (nunca en el secundario) y no ocurriría en un solo sistema de monitor.

El doble buffering del control, usando el ejemplo anterior, resolvió nuestro problema. Valoramos mucho su ayuda.