Comment corriger le scintillement dans les contrôles utilisateur

107

Dans mon application, je passe constamment d'un contrôle à un autre. J'ai créé non. des commandes utilisateur, mais pendant la navigation, mes commandes scintillent. la mise à jour prend 1 ou 2 secondes. J'ai essayé de régler ç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 de fond avec des contrôles différents. Alors, quelle est la solution pour cela ..
Merci.

Royson
la source
Où sont ces déclarations? Idéalement, placez-les dans le constructeur. Avez-vous appelé UpdateStylesaprès avoir défini ces paramètres? C'est mal documenté, mais peut parfois être nécessaire.
Thomas

Réponses:

306

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, BackgroundImage peut faire un beaucoup pire.

Il démarre lorsque UserControl se peint lui-même. Il dessine le BackgroundImage, laissant des trous là où vont les fenêtres de contrôle enfant. Chaque contrôle enfant reçoit alors un message pour se peindre, ils rempliront le trou avec le contenu de leur fenêtre. Lorsque vous disposez de nombreux contrôles, ces trous sont visibles par l'utilisateur pendant un certain temps. Ils sont normalement blancs, contrastant mal avec BackgroundImage lorsqu'il fait sombre. Ou ils peuvent ê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, elle est bloquée par la façon dont Windows rend les fenêtres. Corrigé par WPF btw, il n'utilise pas de fenêtres pour les contrôles enfants. Ce que vous souhaitez, c'est la double mise en 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. Il a cependant des effets secondaires et n'augmente pas réellement la vitesse de 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 à BackgroundImage. Ils peuvent être très coûteux lorsque l'image source est volumineuse et doit être réduite pour s'adapter au contrôle. Modifiez la propriété BackgroundImageLayout sur "Tile". Si cela donne une accélération notable, revenez à votre programme de peinture et redimensionnez l'image pour qu'elle corresponde mieux à la taille de contrôle typique. Ou écrivez du code dans la méthode OnResize () de l'UC pour créer une copie correctement dimensionnée de l'image afin qu'elle n'ait pas à être redimensionnée à chaque fois que le contrôle est repeint. Utilisez le format de pixel Format32bppPArgb pour cette copie, il rend environ 10 fois plus rapide que tout autre format de pixel.

La prochaine chose que vous pouvez faire est d'éviter que les trous ne soient si visibles et contrastent mal avec l'image. Vous pouvez désactiver l'indicateur de style WS_CLIPCHILDREN pour l'UC, l'indicateur qui empêche l'UC de peindre dans la zone où se trouvent 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 pourriez encore les voir se peindre un par un, mais le trou blanc ou noir intermédiaire laid 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. Les étiquettes et PictureBox particulières 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 ().

Hans Passant
la source
Désactivez l'expérience utilisateur améliorée WS_CLIPCHILDREN pour moi.
Mahesh
Absolument parfait! .. Merci beaucoup
AlejandroAlis
8

C'est un vrai problème, et la réponse que Hans Passant a donnée est excellente pour sauver le scintillement. Cependant, il y a des effets secondaires comme il l'a mentionné, et ils peuvent être laids (UI moche). Comme indiqué, "Vous pouvez désactiver l' WS_CLIPCHILDRENindicateur de style pour l'UC", mais cela ne le désactive que pour un UC. Les composants du formulaire principal présentent toujours 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 n'est donc pas peint tant que la souris n'est pas dessus (ou qu'un autre événement le déclenche).

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

Je cherchais donc un moyen de désactiver la WS_CLIPCHILDRENpeinture initiale pour que mon formulaire se charge bien peint, ou mieux encore ne l'activer que lors du redimensionnement de mon formulaire avec de nombreux composants.

L'astuce consiste à faire appeler l'application CreateParamsavec le WS_EX_COMPOSITED/WS_CLIPCHILDRENstyle souhaité . 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 cela fonctionne très bien. Merci AngryHacker!

Je mets l' TurnOnFormLevelDoubleBuffering()appel dans l' ResizeBeginévénement form et TurnOffFormLevelDoubleBuffering()j'appelle le formulaire événement ResizeEnd (ou je le laisse simplement WS_CLIPCHILDRENaprès avoir é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;
    }
user2044810
la source
Votre code n'inclut pas la méthode TurnOnFormLevelDoubleBuffering () ...
Dan W
@DanW Jetez un oeil à l'URL publiée dans cette réponse ( angryhacker.com/blog/archive/2010/07/21/… )
ChrisB
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?
Pratt Hinds
6

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

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 invalidez votre contrôle avec une propriété NeedRepaint

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

Patrick
la source
Il s'agit d'une méthode créative pour simuler un double tampon !. Vous pouvez ajouter if (image != null) image.Dispose();avantimage = new Bitmap...
S.Serpooshan
2

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

revobtz
la source
2

J'ai essayé d'ajouter ceci en commentaire mais je n'ai pas assez de points. C'est la seule chose qui a jamais aidé mes problèmes vacillants tant merci à Hans pour son message. Pour tous ceux qui utilisent le constructeur c ++ comme moi, voici la traduction

Ajoutez la déclaration CreateParams au fichier .h du formulaire principal de votre application, 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);
}
NoComprende
la source
2

Mettez le code ci-dessous dans votre constructeur ou dans l'événement OnLoad et si vous utilisez une sorte de contrôle utilisateur personnalisé ayant des sous-contrôles, vous devrez vous assurer que ces contrôles personnalisés sont également en double tampon (même si dans la documentation MS, ils disent il est défini sur true par défaut).

Si vous créez un contrôle personnalisé, vous souhaiterez peut-être ajouter cet indicateur dans 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 DoubleBufferedpropriété, puis nous le changeons en true afin de rendre chaque contrôle du formulaire double tamponné. La raison pour laquelle nous réfléchissons ici, c'est parce qu'imaginez que vous avez un contrôle qui a des contrôles enfants qui ne sont pas accessibles, de cette façon, même s'il s'agit de contrôles privés, nous changerons toujours leur propriété en true.

Vous trouverez plus d'informations sur la technique de double tamponnage ici .

Il y a une autre propriété que je remplace généralement pour trier 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 la double mise en tampon.

Vous pouvez trouver plus de ces drapeaux de style ici .

J'espère que cela pourra aider!


la source
1

Juste pour ajouter à la réponse de Hans:

(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 aggravé), essayez ceci: parcourez TOUS vos contrôles et tout votre code, et partout où vous avez Toute transparence ou semi-transparence pour BackColor, ForeColor, ou toute autre couleur, supprimez-la simplement, utilisez uniquement des couleurs unies. Dans la plupart des cas où vous pensez que vous ne devez utiliser la transparence, vous ne le faites pas. Reconcevez votre code et vos contrôles et utilisez des couleurs unies. J'avais un scintillement terrible et terrible et le programme fonctionnait lentement. Une fois que j'ai supprimé la transparence, elle s'est considérablement accélérée et il n'y a pas de scintillement.

EDIT: Pour ajouter plus, je viens de découvrir que WS_EX_COMPOSITED n'a pas besoin d'être à l'échelle de la fenêtre, il pourrait être appliqué uniquement à des contrôles spécifiques! Cela m'a évité beaucoup de problèmes. Créez simplement un contrôle personnalisé hérité de tout 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, évitant ainsi les effets secondaires désagréables dans le reste de l'application!

Daniel
la source
0

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 Tabcontrolscintillement dans un formulaire avec un dépassement OnPaintet / ouOnPaintBackGround sous Windows 8 en utilisant .NET 4.0.

La seule pensée qui a fonctionné a été de NE PAS UTILISER la Graphics.DrawImageméthode dans les OnPaintsubstitutions, en d'autres termes, lorsque le dessin a été fait directement sur les graphiques fournis par le PaintEventArgs, même en peignant tout le rectangle, le scintillement a disparu. Mais si appeler leDrawImage méthode, même en dessinant un Bitmap découpé (créé pour une double mise en tampon), le scintillement apparaît.

J'espère que ça aide!

ZeroWorks
la source
0

J'ai combiné ce correctif de scintillement et ce correctif de police , puis j'ai dû ajouter un peu de mon propre code pour démarrer un minuteur sur la peinture pour invalider le TabControl lorsqu'il passe hors é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 j'ai compris, le bitmap évite tout le bogue.

C'était la seule chose qui résolvait définitivement le scintillement de TabControl (avec des icônes) pour moi.

vidéo de résultat de différence: vanilla tabcontrol vs tabcontrolex

http://gfycat.com/FineGlitteringDeermouse

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

utilisateur3732487
la source
-2

Avez-vous essayé Control.DoubleBufferedProperty?

Obtient ou définit une valeur indiquant si ce contrôle doit redessiner sa surface à l'aide d'un tampon secondaire pour réduire ou empêcher le scintillement.

Aussi ceci et cela pourrait aider.

KMån
la source
-9

Il n'y a pas besoin de double tampon et de tout ça les gars ...

Une solution simple ...

Si vous utilisez l'interface MDI, collez simplement le code ci-dessous dans le formulaire principal. Cela supprimera tout scintillement des pages. Cependant, certaines pages qui nécessitent plus de temps pour le chargement apparaîtront dans 1 ou 2 secondes. Mais c'est mieux que d'afficher une page scintillante dans laquelle chaque élément vient un par un.

C'est la seule meilleure solution pour toute l'application. Voir le code à mettre dans le formulaire principal:

protected override CreateParams CreateParams {
  get {
    CreateParams cp = base.CreateParams;
    cp.ExStyle |= 0x02000000;  // Turn on WS_EX_COMPOSITED
    return cp;
  }
} 
Kshitiz
la source
12
Donc, ce que vous dites, c'est que la réponse que Hans a fournie il y a plus de deux ans est en fait correcte? Merci, Kshitiz. C'est vraiment très utile!
Fernando