6 votes

Le propre contrôle WinForms clignote et a de mauvaises performances

J'ai deux problèmes avec un contrôle utilisateur propre qui utilise des bitmaps :

  1. Il clignote s'il est redessiné par la méthode "Refresh" de .NET.
  2. Il a une mauvaise performance.

Le contrôle se compose de trois bitmaps :

  • Une image de fond statique.
  • Un rotor en rotation.
  • Une autre image en fonction de l'angle du rotor.

Tous les bitmaps utilisés ont une résolution de 500x500 pixels. Le contrôle fonctionne comme suit : https://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif (c'est une animation gif)

Le contrôle utilisateur doit se dessiner à chaque fois qu'il reçoit un nouvel angle de rotor. Par conséquent, il possède une propriété publique 'RotorAngle' qui ressemble à ceci :

public double RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Refresh();
    }
}

Refresh soulève le Paint événement. Le site OnPaint Le gestionnaire d'événement ressemble à ceci :

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e)
{
    // Draw the three bitmaps using a rotation matrix to rotate the rotor bitmap.
    Draw((float)mRotorAngle);
}

Mais lorsque j'utilise ce code - qui fonctionne bien dans d'autres contrôles utilisateur - le contrôle utilisateur n'est pas affiché du tout si le contrôle est mis en double tampon via SetStyle(ControlStyles.OptimizedDoubleBuffer, true) . Si je ne mets pas cet indicateur à true, le contrôle clignote lorsqu'il est redessiné.

Dans le constructeur de contrôle, je mets :

SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ContainerControl, false);
// User control is not drawn if "OptimizedDoubleBuffer" is true.
// SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.SupportsTransparentBackColor, true);

Tout d'abord, je pensais que le clignotement était dû au fait que l'arrière-plan est effacé chaque fois que le contrôle est dessiné. Par conséquent, j'ai défini SetStyle(ControlStyles.AllPaintingInWmPaint, true) . Mais ça n'a pas aidé.

Alors, pourquoi ça clignote ? Les autres contrôles fonctionnent très bien avec cette configuration. Et pourquoi le contrôle n'est-il pas affiché si SetStyle(ControlStyles.OptimizedDoubleBuffer, true) .

J'ai découvert que le contrôle ne clignote pas si j'invoque ma méthode de contrôle de la qualité de l'air. Draw directement après avoir modifié la propriété RotorAngle :

public float RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Draw(mRotorAngle);
    }
}

Mais cela se traduit par de très mauvaises performances, notamment en mode plein écran. Il n'est pas possible de mettre à jour le contrôle toutes les 20 millisecondes. Vous pouvez l'essayer vous-même. Je joins ci-dessous la solution complète de Visual Studio 2008.

Alors, pourquoi cette performance est-elle si mauvaise ? Ce n'est pas un problème de mettre à jour d'autres (propres) contrôles toutes les 20 millisecondes. Est-ce vraiment dû aux bitmaps ?

J'ai créé une solution visuelle simple de Visual Studio 2008 pour démontrer les deux problèmes : https://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip (289,3 KB)

Il y a un exécutable dans le répertoire bin\Debug .

Merci pour votre aide.

4voto

lnmx Points 3207

Tout d'abord, conformément à la réponse de LarsTech, vous devez utiliser l'option Graphics contexte fourni dans le PaintEventArgs . En appelant CreateGraphics() à l'intérieur de la Paint vous empêchez OptimizedDoubleBuffer de fonctionner correctement.

Ensuite, dans votre bloc SetStyle, ajoutez :

SetStyle( ControlStyles.Opaque, true );

... pour empêcher la classe de base Control de remplir la couleur de fond avant d'appeler votre gestionnaire Paint.

J'ai testé cela dans votre projet d'exemple, cela semble éliminer le scintillement.

4voto

LarsTech Points 43510

Ne pas utiliser CreateGraphics mais utilisez l'objet graphique qui vous a été transmis par l'événement de peinture :

Je l'ai modifié comme ça et j'ai ajouté une image claire puisque le redimensionnement aurait montré l'image fantôme :

private void Draw(float rotorAngle, Graphics graphics)
{
  graphics.Clear(SystemColors.Control);
  graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

  // yada-yada-yada

  // do not dispose since you did not create it:
  // graphics.Dispose();
}

appelé de :

private void StatorAndRotor2_Paint(object sender, PaintEventArgs e)
{
  Draw((float)mRotorAngle, e.Graphics);
}

Dans le constructeur, activez le double tampon, mais je pense que la transparence n'est pas nécessaire :

SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ContainerControl, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
//SetStyle(ControlStyles.SupportsTransparentBackColor, true);

2voto

Tilak Points 13095

Dessiner à l'écran lors d'un événement Paint est une opération lourde. Peindre dans la mémoire tampon est comparativement très rapide.

Double mise en mémoire tampon améliorera les performances. Au lieu de dessiner sur les graphiques de peinture, créez un nouveau graphique, et faites tous les dessins sur celui-ci.

Une fois le dessin du bitmap terminé, copier le bitmap entier dans e.Graphics reçu de PaintEventArgs.

Dessin sans scintillement à l'aide de GDI+ et de C#

0voto

Fredrik Ljung Points 890

Pour ma réponse, je me suis inspiré de https://stackoverflow.com/a/2608945/455904 et j'ai piqué des trucs dans LarsTechs réponse ci-dessus.

Pour éviter de devoir régénérer l'image complète sur tous les OnPaints, vous pouvez utiliser une variable pour contenir l'image générée.

private Bitmap mtexture;

Utilisez Draw() pour générer la texture

private void Draw(float rotorAngle)
{
    using (var bufferedGraphics = Graphics.FromImage(mtexture))
    {
        Rectangle imagePosition = new Rectangle(0, 0, Width, Height);
        bufferedGraphics.DrawImage(mStator, imagePosition);
        bufferedGraphics.DrawImage(RotateImage(mRotor, mRotorAngle), imagePosition);

        float normedAngle = mRotorAngle % cDegreePerFullRevolution;

        if (normedAngle < 0)
            normedAngle += cDegreePerFullRevolution;

        if (normedAngle >= 330 || normedAngle <= 30)
            bufferedGraphics.DrawImage(mLED101, imagePosition);
        if (normedAngle > 30 && normedAngle < 90)
            bufferedGraphics.DrawImage(mLED001, imagePosition);
        if (normedAngle >= 90 && normedAngle <= 150)
            bufferedGraphics.DrawImage(mLED011, imagePosition);
        if (normedAngle > 150 && normedAngle < 210)
            bufferedGraphics.DrawImage(mLED010, imagePosition);
        if (normedAngle >= 210 && normedAngle <= 270)
            bufferedGraphics.DrawImage(mLED110, imagePosition);
        if (normedAngle > 270 && normedAngle < 330)
            bufferedGraphics.DrawImage(mLED100, imagePosition);
    }
}

Faites en sorte que la surcharge de OnPaint dessine la texture sur votre contrôle.

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Rectangle imagePosition = new Rectangle(0, 0, Width, Height);
    e.Graphics.DrawImage(mtexture, imagePosition);
}

Surchargez OnInvalidated() pour dessiner() la texture quand c'est nécessaire.

protected override void OnInvalidated(InvalidateEventArgs e)
{
    base.OnInvalidated(e);

    if (mtexture != null)
    {
        mtexture.Dispose();
        mtexture = null;
    }

    mtexture = new Bitmap(Width, Height);
    Draw(mRotorAngle);
}

Au lieu d'appeler Draw invalider l'image. Cela la fera repeindre en utilisant OnInvalidated et OnPaint.

public float RotorAngle
{
    get { return mRotorAngle; }
    set
    {
        mRotorAngle = value;
        Invalidate();
    }
}

J'espère que j'ai tout mis là-dedans :)

0voto

Benedikt Points 205

Merci beaucoup pour votre aide.

Le scintillement est résolu :)

Maintenant, je suis LarsTech et utiliser le Graphics l'objet de PaintEventArgs .

Gracias lnmx pour l'indication que CreateGraphics() à l'intérieur de Paint empêche le fonctionnement correct de OptimizedDoubleBuffer . Cela explique le problème de scintillement même si OptimizedDoubleBuffer a été activé. Je ne le savais pas et je n'ai pas non plus trouvé cela à Bibliothèque MSDN . Dans mes contrôles précédents, j'ai également utilisé l'option Graphics l'objet de PaintEventArgs .

Gracias Sallow pour vos efforts. I

Il y avait encore un autre problème de performance dans mon code original.

Modifié

graphics.DrawImage(mStator, imagePosition);
graphics.DrawImage(RotateImage(mRotor, rotorAngle), imagePosition);

a

graphics.DrawImage(mStator, imagePosition);
Bitmap rotatedImage = RotateImage(mRotor, rotorAngle);
graphics.DrawImage(rotatedImage, imagePosition);
rotatedImage.Dispose(); // Important, otherwise the RAM will be flushed with bitmaps.

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