117 votes

Comment corriger le scintillement dans les contrôles utilisateur ?

Dans mon application, je passe constamment d'un contrôle à un autre. J'ai créé un certain nombre de contrôles utilisateur, mais pendant la navigation, mes contrôles clignotent. Il faut 1 ou 2 secondes pour les mettre à jour. J'ai essayé de définir ceci

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
or
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true); 
SetStyle(ControlStyles.DoubleBuffer, true);

mais cela n'a pas aidé... Chaque contrôle a la même image de fond avec des contrôles différents. Alors quelle est la solution pour cela..
Gracias.

0 votes

Où sont ces déclarations ? Idéalement, mettez-les dans le constructeur. Avez-vous appelé UpdateStyles après les avoir réglés ? C'est mal documenté, mais cela peut parfois être nécessaire.

332voto

Hans Passant Points 475940

Ce n'est pas le genre de scintillement que le double tampon peut résoudre. Ni BeginUpdate ou SuspendLayout. Vous avez trop de contrôles, le BackgroundImage peut rendre un lot pire.

Elle commence lorsque le UserControl se peint lui-même. Il dessine l'image d'arrière-plan (BackgroundImage), laissant des trous à l'emplacement des fenêtres des contrôles enfants. Chaque contrôle enfant reçoit alors un message l'invitant à se peindre, il remplit le trou avec le contenu de sa fenêtre. Lorsque vous avez beaucoup de contrôles, ces trous sont visibles par l'utilisateur pendant un certain temps. Ils sont normalement blancs, ce qui contraste fortement avec l'image d'arrière-plan lorsqu'elle est sombre. Ils peuvent aussi être noirs si le formulaire a sa propriété Opacity ou TransparencyKey définie, ce qui contraste mal avec à peu près tout.

Il s'agit d'une limitation assez fondamentale de Windows Forms, il est coincé avec la façon dont Windows rend Windows. Cela a été corrigé par WPF, qui n'utilise pas Windows pour les contrôles enfants. Ce que vous voulez, c'est une double mise en mémoire tampon de l'ensemble du formulaire, y compris les contrôles enfants. C'est possible, vérifiez mon code dans ce fil pour la solution. Elle a cependant des effets secondaires, et n'augmente pas réellement la vitesse de la peinture. Le code est simple, collez ceci dans votre formulaire (pas dans le contrôle utilisateur) :

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 

Il y a beaucoup de choses que vous pouvez faire pour améliorer la vitesse de peinture, au point que le scintillement n'est plus perceptible. Commencez par vous attaquer aux BackgroundImage. Ils peuvent être vraiment coûteux lorsque l'image source est grande et doit être réduite pour s'adapter au contrôle. Changez la propriété BackgroundImageLayout en "Tile". Si vous constatez une accélération notable, retournez dans votre programme de peinture et redimensionnez l'image pour qu'elle corresponde mieux à la taille typique du contrôle. Ou bien, écrivez du code dans la méthode OnResize() de l'UC pour créer une copie de l'image de taille correcte afin qu'il ne soit pas nécessaire de la redimensionner à chaque fois que le contrôle se repeint. Utilisez le format de pixel Format32bppPArgb pour cette copie, il assure un rendu environ 10 fois plus rapide que tout autre format de pixel.

La prochaine chose que vous pouvez faire est d'éviter que les trous soient si visibles et contrastent mal avec l'image. Vous pouvez éteindre l'indicateur de style WS_CLIPCHILDREN pour l'UC, l'indicateur qui empêche l'UC de peindre dans la zone où vont les contrôles enfants. Collez ce code dans le code du UserControl :

protected override CreateParams CreateParams {
  get {
    var parms = base.CreateParams;
    parms.Style &= ~0x02000000;  // Turn off WS_CLIPCHILDREN
    return parms;
  }
}

Les contrôles enfants vont maintenant se peindre par-dessus l'image de fond. Vous pouvez toujours les voir se peindre un par un, mais l'affreux trou intermédiaire blanc ou noir ne sera pas visible.

Enfin, la réduction du nombre de contrôles enfants est toujours une bonne approche pour résoudre les problèmes de peinture lente. Remplacez l'événement OnPaint() de l'UC et dessinez ce qui est maintenant affiché dans un enfant. L'étiquette particulière et la PictureBox sont très gaspilleur. Ils sont pratiques pour le point and click mais leur alternative légère (dessiner une chaîne ou une image) ne prend qu'une seule ligne de code dans votre méthode OnPaint().

1 votes

Désactiver WS_CLIPCHILDREN a amélioré l'expérience utilisateur pour moi.

0 votes

"collez ceci dans votre formulaire (pas le contrôle utilisateur) :" Où devrais-je mettre ce code dans un addin MS Office VSTO ? Mon usercontrol est affiché dans le volet des tâches de MS Office (le panneau latéral).

12voto

user2044810 Points 31

Il s'agit d'un vrai problème, et la réponse donnée par Hans Passant est excellente pour sauver le scintillement. Cependant, il y a des effets secondaires comme il l'a mentionné, et ils peuvent être laids (laideur de l'interface utilisateur). Comme indiqué, "Vous pouvez désactiver le WS_CLIPCHILDREN pour l'UC", mais cela ne le désactive que pour une UC. Les composants du formulaire principal posent toujours problème.

Par exemple, la barre de défilement d'un panneau ne se peint pas, car elle se trouve techniquement dans la zone enfant. Cependant, le composant enfant ne dessine pas la barre de défilement, qui n'est donc pas peinte avant le passage de la souris (ou un autre événement qui la déclenche).

De même, les icônes animées (changement d'icônes dans une boucle d'attente) ne fonctionnent pas. La suppression des icônes dans une tabPage.ImageKey ne redimensionne pas/ne repeint pas les autres tabPages de manière appropriée.

Donc, je cherchais un moyen de désactiver la WS_CLIPCHILDREN lors de la peinture initiale pour que mon formulaire se charge joliment peint, ou mieux encore, ne l'activer que lors du redimensionnement de mon formulaire avec beaucoup de composants.

L'astuce consiste à faire en sorte que l'application appelle CreateParams avec les WS_EX_COMPOSITED/WS_CLIPCHILDREN le style. J'ai trouvé un hack ici ( https://web.archive.org/web/20161026205944/http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-Windows-forms-applications.aspx ) et ça marche très bien. Merci AngryHacker !

J'ai mis le TurnOnFormLevelDoubleBuffering() appel sous la forme ResizeBegin et TurnOffFormLevelDoubleBuffering() dans l'événement ResizeEnd du formulaire (ou laissez simplement l'événement WS_CLIPCHILDREN après qu'il ait été peint correctement au départ).

    int originalExStyle = -1;
    bool enableFormLevelDoubleBuffering = true;

    protected override CreateParams CreateParams
    {
        get
        {
            if (originalExStyle == -1)
                originalExStyle = base.CreateParams.ExStyle;

            CreateParams cp = base.CreateParams;
            if (enableFormLevelDoubleBuffering)
                cp.ExStyle |= 0x02000000;   // WS_EX_COMPOSITED
            else
                cp.ExStyle = originalExStyle;

            return cp;
        }
    }

    public void TurnOffFormLevelDoubleBuffering()
    {
        enableFormLevelDoubleBuffering = false;
        this.MaximizeBox = true;
    }

0 votes

Votre code n'inclut pas la méthode TurnOnFormLevelDoubleBuffering()...

0 votes

@DanW Consultez l'URL publiée dans cette réponse ( angryhacker.com/blog/archive/2010/07/21/ )

0 votes

Le lien dans cette réponse semble être mort. Je suis curieux de connaître la solution, avez-vous un lien vers un autre exemple ?

6voto

Patrick Points 6834

Si vous effectuez une peinture personnalisée dans le contrôle (c'est-à-dire si vous surchargez OnPaint), vous pouvez essayer la double mise en mémoire tampon vous-même.

Image image;
protected override OnPaint(...) {
    if (image == null || needRepaint) {
        image = new Bitmap(Width, Height);
        using (Graphics g = Graphics.FromImage(image)) {
            // do any painting in image instead of control
        }
        needRepaint = false;
    }
    e.Graphics.DrawImage(image, 0, 0);
}

Et invalider votre contrôle avec une propriété NeedRepaint

Sinon, la réponse ci-dessus avec SuspendLayout et ResumeLayout est probablement ce que vous voulez.

0 votes

Il s'agit d'une méthode créative pour simuler le double tampon ! Vous pouvez ajouter if (image != null) image.Dispose(); avant image = new Bitmap...

3voto

Web World Points 3221

2voto

revobtz Points 1

Sur le formulaire principal ou le contrôle utilisateur où se trouve l'image d'arrière-plan, définissez l'attribut BackgroundImageLayout à la propriété Center o Stretch . Vous remarquerez une grande différence lors du rendu du contrôle de l'utilisateur.

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