84 votes

Horrible performance de redessinage du DataGridView sur l'un de mes deux écrans

J'ai déjà 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 par un repeint EXTRÊMEMENT lent du contrôle ( comme 30 secondes pour une peinture complète ), mais seulement lorsqu'il est sur l'un de mes écrans. Quand il est sur l'autre, la vitesse de repeinte est bonne.

J'ai une Nvidia 8800 GT avec les derniers pilotes non-beta (175. quelque chose). S'agit-il d'un bug du pilote ? Je laisse la question en suspens, puisque je dois vivre avec cette configuration particulière. (Cela ne se produit pas sur les cartes ATI, cependant...)

La vitesse de peinture n'a rien à voir avec le contenu des cellules, et le dessin personnalisé n'améliore pas du tout les performances, même lorsqu'il s'agit de peindre un rectangle plein.

Je découvre par la suite que le fait de placer un ElementHost (de l'espace de noms System.Windows.Forms.Integration) sur le formulaire corrige le problème. Il n'est pas nécessaire de le manipuler ; il suffit qu'il soit un enfant du formulaire sur lequel se trouve également le DataGridView. Il peut être redimensionné à (0, 0) tant que l'élément Visible est vrai.

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

Cette méthode me permet également d'appliquer le correctif pendant que l'application est en cours d'exécution, ce qui permet de voir plus facilement ce que les bibliothèques WPF modifient sur mon formulaire (à l'aide de Spy++).

Après de nombreux essais et erreurs, j'ai remarqué que l'activation de la double mise en mémoire tampon sur le contrôle lui-même (par opposition au formulaire) corrige le problème !


Il vous suffit donc de créer une classe personnalisée basée sur DataGridView pour pouvoir activer son DoubleBuffering. Voilà, c'est fait !

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 me retrouve dans une situation où je ne peux pas utiliser la solution de la sous-classe (si je n'ai pas le code), je suppose que je pourrais essayer d'injecter ce contrôle dans le formulaire :) ( bien que je serai plus enclin à essayer d'utiliser la réflexion pour forcer la propriété DoubleBuffered de l'extérieur afin d'éviter une fois de plus la dépendance ).

C'est triste qu'une chose aussi trivialement simple ait pris autant de temps...

1 votes

Nous avons eu un problème similaire avec des clients qui ont Multimon installé. Pour une raison quelconque, quand ils désactivent Multimon, le problème disparaît.

0 votes

Quelqu'un est-il au courant et peut-il expliquer pourquoi cela se produit et pourquoi DoubleBuffered ne peut être activé par défaut ?

66voto

Benoit Points 12985

Il vous suffit de créer une classe personnalisée basée sur DataGridView pour pouvoir activer son DoubleBuffering. Voilà, c'est fait !

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 me retrouve dans une situation où je ne peux pas utiliser la solution de la sous-classe (si je n'ai pas le code), je suppose que je pourrais essayer d'injecter ce contrôle dans 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 éviter une fois de plus la dépendance).

C'est triste qu'une chose aussi trivialement simple ait pris autant de temps...

Note : Faire de la réponse une réponse pour que la question puisse être marquée comme répondue.

1 votes

Comment faire cela avec l'intégration de Windows Forms pour WPF ?

0 votes

Merci pour la réponse. Comment se fait-il que vous ne puissiez parfois pas utiliser la solution des sous-classes ? (Je n'ai pas compris le passage "si je n'ai pas le code").

0 votes

Fantastique ! Fonctionne comme un charme dans mon projet qui souffrait d'un ralentissement bizarre lors du remplissage et du défilement du tableau ( :

62voto

Brian Ensink Points 7579

Voici un code qui définit la propriété en utilisant la réflexion, sans sous-classer comme Benoit le suggère.

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

3 votes

Heureux d'aider ! J'ai failli ne pas le poster car cette question datait déjà d'un an.

1 votes

Neah, ça aidera toujours quelqu'un dans le futur, comme peut-être même moi, qui vient de trouver ce fil de discussion à partir de Google. Merci ! Btw, est-il préférable de mettre ceci dans la section Form1_Load ?

2 votes

Juste pour donner une idée à quelqu'un d'autre qui trouve cela : C'est une méthode d'extension utile sur le Control classe. public static void ToggleDoubleBuffered(this Control control, bool isDoubleBuffered) .

19voto

GELR Points 94

Pour les personnes qui cherchent comment le faire en 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})

11voto

brtmckn Points 11

En complément des posts précédents, pour les applications Windows Forms, c'est ce que j'utilise pour les composants DataGridView afin de les rendre rapides. Le code pour 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 d'effectuer des mises à jour de données importantes.

Appelez DrawingControl.ResumeDrawing(control) après avoir effectué des mises à jour de données importantes.

Ces deux derniers sont mieux réalisés avec un bloc try/finally. (ou mieux encore, réécrire la classe en tant que IDisposable et appeler SuspendDrawing() dans le constructeur et 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();
    }
}

7voto

Kev Points 718

La réponse à cette question a fonctionné pour moi aussi. J'ai pensé ajouter un raffinement qui, selon moi, devrait être une pratique standard pour quiconque met en œuvre cette solution.

La solution fonctionne bien, sauf lorsque l'interface utilisateur est exécutée en tant que session client dans le cadre d'un bureau à distance, notamment lorsque la bande passante du réseau disponible est faible. Dans ce cas, l'utilisation de la double mise en mémoire tampon peut nuire aux performances. 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 au bureau à distance

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X