Rendimiento de redibujado horrible de DataGridView en una de mis dos pantallas

81

De hecho, resolví esto, pero lo voy a publicar 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 repintado EXTREMADAMENTE lento del control ( como 30 segundos para un repintado completo ), pero solo cuando está en una de mis pantallas. Cuando por el otro lado, la velocidad de repintado es buena.

Tengo una Nvidia 8800 GT con los últimos controladores no beta (175. algo). ¿Es un error de controlador? Lo dejaré en el aire, ya que tengo que vivir con esta configuración en particular. (Sin embargo, no sucede con las tarjetas ATI ...)

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

Más tarde descubrí que colocar un ElementHost (del espacio de nombres System.Windows.Forms.Integration) en el formulario corrige el problema. No tiene por qué meterse con él; solo necesita ser un elemento secundario del formulario en el que también se encuentra DataGridView. Se puede cambiar el tamaño a (0, 0) siempre que la propiedad Visible sea ​​verdadera.

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

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

Después de muchas pruebas y errores, noté que habilitar el almacenamiento en búfer doble en el control en sí (en lugar de solo en el formulario) corrige el problema.


Por lo tanto, solo necesita crear una clase personalizada basada en DataGridView para poder habilitar su DoubleBuffering. ¡Eso es!

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

Siempre que todas mis instancias de la cuadrícula 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 la reflexión para forzar la propiedad DoubleBuffered desde el exterior para evitar una vez más la dependencia ).

Es triste que algo tan trivialmente simple haya consumido tanto de mi tiempo ...

Corey Ross
fuente
1
Tuvimos un problema similar con los clientes que tienen instalado Multimon . Por alguna razón, cuando apagan Multimon, el problema desaparece.
BlueRaja - Danny Pflughoeft
¿Alguien sabe y puede explicar por qué sucede esto y por qué DoubleBuffered no se puede activar de forma predeterminada?
Vojtěch Dohnal

Respuestas:

64

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


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

Siempre que todas mis instancias de la cuadrícula 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 la reflexión para forzar la propiedad DoubleBuffered desde el exterior para evitar una vez más la dependencia).

Es triste que algo tan trivialmente simple haya consumido tanto de mi tiempo ...

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

Benoit
fuente
1
¿Cómo puede hacer esto con la integración de Windows Forms para WPF?
Parcial
Gracias por la respuesta. ¿Cómo es posible que a veces no pueda utilizar la solución de subclase? (No entendí el bit "si no tengo el código").
Dan W
¡Fantástico! Funciona como un encanto en mi proyecto que sufría una desaceleración extraña tanto al poblar como al desplazarse por la tabla (:
knut
61

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

typeof(DataGridView).InvokeMember(
   "DoubleBuffered", 
   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
   null, 
   myDataGridViewObject, 
   new object[] { true });
Brian Ensink
fuente
3
¡Encantado de ayudar! Casi no lo publico porque esta pregunta ya tenía un año.
Brian Ensink
1
Neah, siempre ayudará a alguien en el futuro, como tal vez incluso a mí, que acaba de encontrar este hilo de Google. ¡Gracias! Por cierto, ¿es preferible poner esto en la sección Form1_Load?
Dan W
2
Solo para darle una idea a alguien que encuentre esto: este es un método de extensión útil en la Controlclase. public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered).
Anthony
¿Se puede colocar en el evento de carga de FOrm donde se coloca la vista de cuadrícula de datos?
Arie
18

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})
GELR
fuente
10

Agregando a publicaciones anteriores, para las aplicaciones de Windows Forms, esto es lo que uso para que los componentes DataGridView los hagan más rápidos. El código para la clase DrawingControl se encuentra a continuación.

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 realizar actualizaciones de big data.

Estos 2 últimos se hacen mejor con un bloqueo try / finalmente. (o incluso mejor reescriba la clase como IDisposabley llame SuspendDrawing()al constructor y 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;

    /// <summary>
    /// 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().
    /// </summary>
    /// <param name="control">The Control on which to set DoubleBuffered to true.</param>
    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 });
        }
    }

    /// <summary>
    /// Suspend drawing updates for the specified control. After the control has been updated
    /// call DrawingControl.ResumeDrawing(Control control).
    /// </summary>
    /// <param name="control">The control to suspend draw updates on.</param>
    public static void SuspendDrawing(Control control)
    {
        SendMessage(control.Handle, WM_SETREDRAW, false, 0);
    }

    /// <summary>
    /// Resume drawing updates for the specified control.
    /// </summary>
    /// <param name="control">The control to resume draw updates on.</param>
    public static void ResumeDrawing(Control control)
    {
        SendMessage(control.Handle, WM_SETREDRAW, true, 0);
        control.Refresh();
    }
}
brtmckn
fuente
7

La respuesta a esto también funcionó para mí. Pensé en agregar 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 interfaz de usuario se ejecuta como una sesión de cliente en un 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 búfer. 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 detalles, consulte Detectar la conexión de escritorio remoto

Kev
fuente
1

Encontré una solución al problema. Vaya a la pestaña de resolución de problemas en las propiedades de pantalla avanzadas y verifique el control deslizante de aceleración de hardware. Cuando recibí mi nueva PC de la empresa de TI, estaba configurada a un tick de completa y no tuve ningún problema con las redes de datos. Una vez que actualicé el controlador de la tarjeta de video y lo configuré al máximo, la pintura de los controles de la cuadrícula de datos se volvió muy lenta. Así que lo restablecí a donde estaba y el problema desapareció.

Espero que este truco funcione para ti también.


fuente
1

Solo para agregar lo que hicimos para solucionar este problema: Actualizamos a los últimos controladores de Nvidia que resolvieron el problema. No fue necesario reescribir ningún código.

Para completar, la tarjeta era una Nvidia Quadro NVS 290 con controladores con fecha de marzo de 2008 (v. 169). La actualización a la última (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).

Richard Morgan
fuente
1

¡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
usuario3727004
fuente
0

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

Nuestra aplicación mostraría la cuadrícula con un fondo gris, lo que indica que las celdas no se pueden cambiar. Al seleccionar un botón "cambiar configuración", el programa cambiaría el color de fondo de las celdas a blanco para indicarle 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 principal (nunca en el secundario) y no ocurriría en un sistema de un solo monitor.

El doble almacenamiento en búfer del control, usando el ejemplo anterior, resolvió nuestro problema. Agradecemos mucho su ayuda.


fuente