web-dev-qa-db-fra.com

Comment corriger le scintillement dans les contrôles utilisateur

Dans mon application, je passe constamment d'un contrôle à un autre. J'ai créé non. des commandes utilisateur, mais lors de la navigation, mes commandes deviennent scintillantes. il faut 1 ou 2 secondes pour mettre à jour. J'ai essayé de mettre ça

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 d'arrière-plan avec des contrôles différents. Alors, quelle est la solution pour cela ..
Merci.

103
Royson

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

Cela commence quand UserControl se peint. Il dessine BackgroundImage, laissant des trous à la place des fenêtres de contrôle enfants. Chaque contrôle enfant reçoit alors un message à Paint lui-même, ils vont remplir le trou avec le contenu de leur fenêtre. Lorsque vous avez beaucoup de contrôles, ces trous sont visibles par l'utilisateur pendant un certain temps. Ils sont normalement blancs, contrastant mal avec BackgroundImage quand il fait noir. Ils peuvent aussi être noirs si la propriété a sa propriété Opacity ou TransparencyKey définie, contrastant mal avec à peu près n'importe quoi.

Il s’agit d’une limitation assez fondamentale de Windows Forms, elle est liée à la manière dont Windows affiche les fenêtres. Corrigé par WPF, il n’utilise pas Windows pour les contrôles enfants. Ce que vous voudriez, c'est mettre en double tampon tout le formulaire, y compris les contrôles enfants. C'est possible, vérifiez mon code dans ce fil pour la solution. Il a cependant des effets secondaires et n'augmente pas réellement la vitesse de peinture. Le code est simple, collez-le dans votre formulaire (pas 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 à BackgroundImage. Elles peuvent être très coûteuses lorsque l’image source est grande et doit être réduite pour correspondre au contrôle. Modifiez la propriété BackgroundImageLayout en "Mosaïque". Si cela accélère sensiblement, revenez dans votre programme de peinture et redimensionnez l'image pour qu'elle corresponde mieux à la taille de contrôle habituelle. Vous pouvez également écrire du code dans la méthode OnResize () de l'UC pour créer une copie correctement dimensionnée de l'image, de sorte qu'elle ne doive pas être redimensionnée à chaque fois que le contrôle est repeint. Utilisez le format de pixel Format32bppPArgb pour cette copie. Le rendu est environ 10 fois plus rapide que tout autre format de pixel.

La prochaine chose que vous pouvez faire est d’empêcher les trous d’être aussi visibles et de contraster avec l’image. Vous pouvez désactiver l'indicateur de style WS_CLIPCHILDREN pour l'UC, l'indicateur empêchant l'UC de peindre dans la zone où vont les contrôles enfants. Collez ce code dans le code de 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 au-dessus de l'image d'arrière-plan. Vous pouvez toujours les voir se peindre un à un, mais le trou blanc ou noir intermédiaire ne sera pas visible.

Dernier point mais non le moindre, réduire le nombre de contrôles enfants est toujours une bonne approche pour résoudre les problèmes de peinture lents. Remplacez l'événement OnPaint () de l'UC et dessinez ce qui est maintenant affiché dans un enfant. Label particulier et PictureBox sont très inutiles. Pratique pour pointer et cliquer, 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 ().

297
Hans Passant

Il s’agit là d’un véritable problème et la réponse donnée par Hans Passant est excellente pour la sauvegarde du scintillement. Cependant, il a des effets secondaires, comme il l'a mentionné, et ils peuvent être laids (UI laid). Comme indiqué, "Vous pouvez désactiver l'indicateur de style WS_CLIPCHILDREN pour l'UC", mais cela ne le désactive que pour un UC. Les composants de la fiche principale ont encore des problèmes.

Par exemple, une barre de défilement de panneau ne peint pas, car elle se trouve techniquement dans la zone enfant. Cependant, le composant enfant ne dessine pas la barre de défilement, il ne sera donc pas peint avant le survol de la souris (ou un autre événement le déclenchera).

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

Je recherchais donc un moyen de désactiver WS_CLIPCHILDREN lors de la peinture initiale afin que ma fiche soit chargée correctement, ou, mieux encore, ne l'active que lorsque vous redimensionnez ma fiche avec beaucoup de composants.

L'astuce consiste à faire en sorte que l'application appelle CreateParams avec le style souhaité de WS_EX_COMPOSITED/WS_CLIPCHILDREN? J'ai trouvé un hack ici ( http://www.angryhacker.com/blog/archive/2010/07/21/how-to-get-rid-of-flicker-on-windows-forms-applications. aspx ) et cela fonctionne très bien. Merci AngryHacker!

Je mets l'appel TurnOnFormLevelDoubleBuffering () sous la forme événement ResizeBegin. Appel TurnOffFormLevelDoubleBuffering () sous la forme événement ResizeEnd (ou laissez-le simplement WS_CLIPCHILDREN après qu’il ait été initialement peint correctement.)

    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;
    }
9
user2044810

Si vous faites un dessin personnalisé dans le contrôle (c'est-à-dire, remplacez OnPaint), vous pouvez essayer la mise en mémoire tampon double 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 invalide ton contrôle avec une propriété NeedRepaint

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

6
Patrick
3
Brij

J'ai essayé d'ajouter ceci comme commentaire mais je n'ai pas assez de points. C’est la seule chose qui ait jamais aidé mes problèmes vacillants à remercier chaleureusement Hans pour son message. Pour ceux qui utilisent c ++ builder comme moi, voici la traduction

Ajoutez la déclaration CreateParams au fichier .h de votre formulaire principal, par exemple.

class TYourMainFrom : public TForm
{
protected:
    virtual void __fastcall CreateParams(TCreateParams &Params);
}

et ajoutez ceci à votre fichier .cpp

void __fastcall TYourMainForm::CreateParams(TCreateParams &Params)
{
    Params.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    TForm::CreateParams(Params);
}
2
NoComprende

Placez le code ci-dessous dans votre constructeur ou votre événement OnLoad. Si vous utilisez une sorte de contrôle utilisateur personnalisé doté de sous-contrôles, vous devez vous assurer que ces contrôles personnalisés sont également dotés d'une double mémoire tampon (même si dans la documentation MS il est défini sur true par défaut).

Si vous créez un contrôle personnalisé, vous voudrez peut-être ajouter cet indicateur à votre ctor:

SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

Vous pouvez éventuellement utiliser ce code dans votre formulaire/contrôle:

foreach (Control control in Controls)
{
    typeof(Control).InvokeMember("DoubleBuffered",
        BindingFlags.SetProperty | BindingFlags.Instance | BindingFlags.NonPublic,
        null, control, new object[] { true });
}

Nous parcourons tous les contrôles du formulaire/contrôle et accédons à leur propriété DoubleBuffered, puis nous le changeons en true afin que chaque contrôle du formulaire soit mis en double tampon. Si nous réfléchissons ici, c'est parce que vous avez un contrôle dont les contrôles enfants ne sont pas accessibles. Ainsi, même s'il s'agit de contrôles privés, nous leur attribuerons la valeur true.

Plus d'informations sur la technique de double tampon peuvent être trouvées ici .

Il y a une autre propriété que je remplace habituellement pour régler ce problème:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams parms = base.CreateParams;
        parms.ExStyle |= 0x00000020; // WS_EX_COMPOSITED
        return parms;
    }
}

WS_EX_COMPOSITED - Peint tous les descendants d’une fenêtre dans l’ordre de peinture de bas en haut en utilisant le double tampon.

Vous pouvez trouver plus de ces drapeaux de style ici .

J'espère que ça t'as aidé!

2
user7867434

Sur le formulaire ou le contrôle utilisateur principal où l'image d'arrière-plan réside, définissez la propriété BackgroundImageLayout sur Center ou Stretch. Vous remarquerez une grande différence lorsque le contrôle utilisateur effectue le rendu.

2
revobtz

Juste pour ajouter à la réponse de Hans a donné:

(Version TLDR: la transparence est plus lourde que vous ne le pensez, utilisez uniquement des couleurs unies partout)

Si WS_EX_COMPOSITED, DoubleBuffered et WS_CLIPCHILDREN n'ont pas résolu votre scintillement (pour moi, WS_CLIPCHILDREN l'a encore aggravé), essayez ceci: passez par TOUTES vos commandes et tout votre code, et partout où vous avez Toute transparence ou semi-transparence pour BackColor, ForeColor, ou toute autre couleur, il suffit de l'enlever, utilisez uniquement des couleurs unies. Dans la plupart des cas où vous pensez que vous venez de utiliser la transparence, vous ne le faites pas. Re-concevez votre code et vos contrôles et utilisez des couleurs unies. J'ai eu un horrible scintillement et le programme était lent. Une fois que j'ai supprimé la transparence, elle s'est accélérée de manière significative, et il n'y a aucun scintillement.

EDIT: Pour ajouter quelque chose, je viens de découvrir que WS_EX_COMPOSITED n’a pas besoin d’être à l’échelle de la fenêtre, il peut s’appliquer uniquement à des contrôles spécifiques! Cela m'a évité beaucoup de problèmes. Créez simplement un contrôle personnalisé hérité du contrôle dont vous avez besoin et collez le remplacement déjà publié pour WS_EX_COMPOSITED. De cette façon, vous obtenez un double tampon de bas niveau sur ce contrôle uniquement, en évitant les effets secondaires désagréables du reste de l'application!

1
Daniel

Je sais que cette question est très ancienne, mais je veux donner mon expérience à ce sujet.

J'ai eu beaucoup de problèmes avec le scintillement de Tabcontrol dans un formulaire avec OnPaint et/ou OnPaintBackGround surchargés dans Windows 8 à l'aide de .NET 4.0.

La seule chose qui a fonctionné a été NOT USE le Graphics.DrawImage La méthode _ dans OnPaint annule, c'est-à-dire que lorsque le dessin a été effectué directement sur les graphiques fournis par le PaintEventArgs, même en peignant tout le rectangle, le scintillement a disparu. Mais si vous appelez la méthode DrawImage, même en dessinant un bitmap tronqué (créé pour une double mise en mémoire tampon), le scintillement apparaît.

J'espère que ça aide!

0
ZeroWorks

J'ai combiné ce correctif de scintillement et ce correctif de police , puis je devais ajouter un peu de mon propre code pour démarrer une minuterie sur Paint afin d'invalider le TabControl lorsqu'il passe à l'écran et retour, etc.

Tous les trois font ceci:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class TabControlEx:TabControl
{
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    private const int WM_Paint = 0x0f;
    private const int WM_SETFONT = 0x30;
    private const int WM_FONTCHANGE = 0x1d;
    private System.Drawing.Bitmap buffer;
    private Timer timer = new Timer();
    public TabControlEx()
    {
        timer.Interval = 1;
        timer.Tick += timer_Tick;
        this.SetStyle(ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }
    void timer_Tick(object sender, EventArgs e)
    {
        this.Invalidate();
        this.Update();
        timer.Stop();
    }
    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_Paint) timer.Start();
        base.WndProc(ref m);
    }
    protected override void OnPaint(PaintEventArgs pevent)
    {
        this.SetStyle(ControlStyles.UserPaint, false);
        base.OnPaint(pevent);
        System.Drawing.Rectangle o = pevent.ClipRectangle;
        System.Drawing.Graphics.FromImage(buffer).Clear(System.Drawing.SystemColors.Control);
        if (o.Width > 0 && o.Height > 0)
        DrawToBitmap(buffer, new System.Drawing.Rectangle(0, 0, Width, o.Height));
        pevent.Graphics.DrawImageUnscaled(buffer, 0, 0);
        this.SetStyle(ControlStyles.UserPaint, true);
    }

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        buffer = new System.Drawing.Bitmap(Width, Height);
    }
    protected override void OnCreateControl()
    {
        base.OnCreateControl();
        this.OnFontChanged(EventArgs.Empty);
    }
    protected override void OnFontChanged(EventArgs e)
    {
        base.OnFontChanged(e);
        IntPtr hFont = this.Font.ToHfont();
        SendMessage(this.Handle, WM_SETFONT, hFont, (IntPtr)(-1));
        SendMessage(this.Handle, WM_FONTCHANGE, IntPtr.Zero, IntPtr.Zero);
        this.UpdateStyles();
    }
}

Je ne suis pas le créateur, mais d'après ce que je comprends, le bitmap contourne tous les bogues.

C’est la seule chose qui résout définitivement TabControl (avec icônes) pour moi.

vidéo sur le résultat de la différence: Vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

ps. vous devrez définir HotTrack = true, car cela corrige aussi ce bogue

0
user3732487