Horrible réécriture de la performance de DataGridView sur l'un de mes deux écrans

j'ai résolu ce problème, mais je le poste pour la postérité.

j'ai rencontré un problème très étrange avec le DataGridView sur mon système à double moniteur. Le problème se manifeste comme un repaint extrêmement lent de la commande ( comme 30 secondes pour un repaint complet ), mais seulement quand il est sur un de mes écrans. De l'autre, la vitesse de repeindre est très bien.

j'ai un NVIDIA 8800 GT avec les derniers pilotes non-beta (175. quelque.) Est-il un pilote bug? Je vais laisser ça en l'air, car je dois vivre avec cette configuration particulière. (Cela ne se produit pas sur les cartes ATI, cependant...)

la vitesse de la peinture n'a rien à voir avec le contenu de la cellule, et le dessin personnalisé n'améliore pas la performance du tout - même quand juste peindre un rectangle solide.

je découvre plus tard que placer un ElementHost (à partir du système.Windows.Forme.Integration namespace) sur le formulaire corrige problème. Il n'a pas à être essayé; il a juste besoin d'être un enfant de la forme, le DataGridView est aussi sur. Il peut être redimensionné à (0, 0) aussi longtemps que la propriété Visible est vraie.

Je ne veux pas ajouter explicitement le .NET 3/3.5 dépendance à mon application; je fais une méthode pour créer ce contrôle à l'exécution (si elle le peut) en utilisant la réflexion. Il fonctionne, et au moins il échoue gracieusement sur les machines qui n'ont pas la bibliothèque requise - il juste remonte à être lent.

cette méthode me permet également de m'appliquer à corriger pendant que l'application est en cours d'exécution, ce qui rend plus facile de voir ce que les bibliothèques WPF changent sur mon formulaire (en utilisant Spy++).

après beaucoup d'essais et d'erreurs, je remarque que l'activation d'un double tampon sur le contrôle lui-même (par opposition à la forme) corrige le problème!


ainsi, vous avez juste besoin de faire une classe personnalisée basée hors de DataGridView ainsi vous pouvez activer son DoubleBuffering. Ça y est!

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

tant que toutes mes instances de la grille utilisent cette version personnalisée, tout va bien. Si jamais je tombe dans une situation causée par ceci où je ne suis pas en mesure d'utiliser la solution de sous-classe (si je n'ai pas le code), je suppose que je pourrais essayer d'injecter ce contrôle sur le formulaire :) ( bien que je serai plus susceptible d'essayer d'utiliser la réflexion pour forcer la propriété DoubleBuffered de l'extérieur pour une fois de plus évitez la dépendance ).

c'est triste qu'une chose aussi banale ait mangé autant de mon temps...

78
demandé sur Peter Mortensen 2008-09-23 05:01:09

9 réponses

vous avez juste besoin de faire une classe personnalisée basée hors de DataGridView afin que vous puissiez activer son DoubleBuffering. Ça y est!


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

tant que toutes mes instances de la grille utilisent cette version personnalisée, tout va bien. Si jamais je tombe dans une situation causée par cela où je ne suis pas capable d'utiliser la solution de sous-classe (si je n'ai pas le code), je suppose que je pourrais essayer d'injecter ce contrôle sur le formulaire :) (bien que je serai plus susceptible d'essayer d'utiliser la réflexion pour forcer la propriété DoubleBuffered sur de l'extérieur pour une fois de plus éviter la dépendance).

c'est triste qu'une chose aussi banale ait mangé autant de mon temps...

Remarque: la réponse une réponse si la question peut être marqué comme réponse

61
répondu Benoit 2008-10-01 12:41:14

voici un code qui définit la propriété en utilisant la réflexion, sans subclassing comme le suggère Benoit.

typeof(DataGridView).InvokeMember(
   "DoubleBuffered", 
   BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
   null, 
   myDataGridViewObject, 
   new object[] { true });
58
répondu Brian Ensink 2009-10-01 19:49:51

pour les personnes cherchant comment faire VB.NET, voici le code:

DataGridView1.GetType.InvokeMember("DoubleBuffered", Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.SetProperty, Nothing, DataGridView1, New Object() {True})
13
répondu GELR 2017-12-14 22:02:35

Ajouter aux messages précédents, pour les applications de formulaires Windows c'est ce que j'utilise pour les composants de DataGridView pour les rendre rapides. Le code de la classe DrawingControl est ci-dessous.

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

Appelez DrawingControl.SetDoubleBuffered (control) après InitializeComponent () dans le constructeur.

Appelez DrawingControl.SuspendDrawing (control) avant de faire des mises à jour de big data.

Appelez DrawingControl.ResumeDrawing (contrôle) après avoir fait des mises à jour de big data.

ces 2 derniers sont mieux faits avec un essai/bloc final. (ou encore mieux réécrire la classe comme IDisposable et appeler SuspendDrawing() dans le constructeur et ResumeDrawing() dans 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();
    }
}
8
répondu brtmckn 2017-09-14 05:46:22

la réponse à cela a fonctionné pour moi aussi. J'ai pensé que je voudrais ajouter un raffinement qui je pense devrait être la pratique standard pour quiconque mettant en œuvre la solution.

la solution fonctionne bien sauf lorsque L'interface utilisateur est exécutée comme une session client sous un bureau distant, en particulier lorsque la bande passante réseau disponible est faible. Dans ce cas, la performance peut être aggravée par l'utilisation d'un double tampon. Par conséquent, je suggère ce qui suit comme une réponse plus complète:

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

Pour plus de détails, reportez-vous à Détection de la connexion bureau à distance

6
répondu Kev 2017-05-23 12:34:31

j'ai trouvé une solution au problème. Allez dans l'onglet Dépannage dans les propriétés d'affichage avancées et vérifiez le curseur d'accélération matérielle. Lorsque j'ai obtenu mon nouveau PC d'entreprise à partir de celui-ci, il a été réglé à une tique de full et je n'ai pas eu de problèmes avec datagrids. Une fois que j'ai mis à jour le pilote de la carte vidéo et que je l'ai réglé à plein, la peinture des commandes de datagrid est devenue très lente. J'ai donc réinitialiser retour à l'endroit où il était, et le problème a disparu.

espérons que ce tour fonctionne pour vous comme bien.

1
répondu 2009-10-07 20:28:17

juste pour ajouter ce que nous avons fait pour corriger ce problème: nous avons mis à niveau vers les derniers pilotes Nvidia résolu le problème. Aucun code n'a dû être réécrit.

par souci d'exhaustivité, la carte était une NVidia Quadro NVS 290 avec pilotes datée de mars 2008 (v. 169). La mise à niveau à la plus récente (v. 182 datée de février 2009) a considérablement amélioré les événements de peinture pour tous mes contrôles, en particulier pour le DataGridView.

produire.)

1
répondu Richard Morgan 2017-12-14 22:01:27

le Meilleur!:

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
1
répondu user3727004 2017-12-14 22:04:28

nous avons connu un problème similaire en utilisant .NET 3.0 et DataGridView sur un système de double moniteur.

notre application afficherait la grille avec un fond gris, indiquant que les cellules ne pouvaient pas être changées. En sélectionnant un bouton "Modifier les paramètres", le programme changerait la couleur de fond des cellules blanches pour indiquer à l'utilisateur que le texte de la cellule pourrait être modifié. Un bouton "cancel" modifierait la couleur de fond des cellules susmentionnées. retour vers le gris.

comme la couleur de fond changée il y aurait un scintillement, une brève impression d'une grille de taille par défaut avec le même nombre de lignes et de colonnes. Ce problème ne se poserait que sur le moniteur primaire (et non sur le moniteur secondaire) et ne se poserait pas sur un système de moniteur unique.

double tampon la commande, en utilisant l'exemple ci-dessus, a résolu notre problème. Nous avons grandement apprécié votre aide.

0
répondu 2008-10-07 19:45:51