webentwicklung-frage-antwort-db.com.de

wie man das Flimmern von C # winforms aufhält

Ich habe ein Programm, das im Wesentlichen wie eine Paint-Anwendung ist. Mein Programm hat jedoch einige Probleme mit dem Flimmern. Ich habe die folgende Zeile in meinem Code (die das Flimmern beseitigen sollte - aber nicht): 

this.SetStyle(ControlStyles.AllPaintingInWmPaint 
| ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

mein Code (abzüglich der Super- und Sub-Klassen für die Shapes lautet wie folgt:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Paint
{
    public partial class Paint : Form
    {
        private Point startPoint;
        private Point endPoint;
        private Rectangle rect = new Rectangle();
        private Int32 brushThickness = 0;
        private Boolean drawSPaint = false;
        private List<Shapes> listOfShapes = new List<Shapes>();
        private Color currentColor;
        private Color currentBoarderColor;
        private Boolean IsShapeRectangle = false;
        private Boolean IsShapeCircle = false;
        private Boolean IsShapeLine = false;

        public SPaint()
        {

            InitializeComponent();
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true);

            currentColor = Color.Red;
            currentBoarderColor = Color.DodgerBlue;
            IsShapeRectangle = true; 
        }

        private void panelArea_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = panelArea.CreateGraphics();

            if (drawSPaint == true)
            {

                Pen p = new Pen(Color.Blue);
                p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

                if (IsShapeRectangle == true)
                {
                    g.DrawRectangle(p, rect);
                }
                else if (IsShapeCircle == true)
                {
                    g.DrawEllipse(p, rect);
                }
                else if (IsShapeLine == true)
                {
                    g.DrawLine(p, startPoint, endPoint);
                }
            }
            foreach (Shapes shape in listOfShapes)
            {

                shape.Draw(g);

            }
        }

        private void panelArea_MouseDown(object sender, MouseEventArgs e)
        {

            startPoint.X = e.X;
            startPoint.Y = e.Y;

            drawSPaint = true;
        }

        private void panelArea_MouseMove(object sender, MouseEventArgs e)
        {


            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {

                if (e.X > startPoint.X)
                {
                    rect.X = startPoint.X;
                    rect.Width = e.X - startPoint.X;
                }
                else
                {
                    rect.X = e.X;
                    rect.Width = startPoint.X - e.X;
                }
                if (e.Y > startPoint.Y)
                {
                    rect.Y = startPoint.Y;
                    rect.Height = e.Y - startPoint.Y;
                }
                else
                {
                    rect.Y = e.Y;
                    rect.Height = startPoint.Y - e.Y;
                }


                panelArea.Invalidate();

            }

        }

        private void panelArea_MouseUp(object sender, MouseEventArgs e)
        {

            endPoint.X = e.X;
            endPoint.Y = e.Y;

            drawSPaint = false;

            if (rect.Width > 0 && rect.Height > 0)
            {
                if (IsShapeRectangle == true)
                {
                    listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeCircle == true)
                {
                    listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
                }
                else if (IsShapeLine == true)
                {
                    listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
                }

                panelArea.Invalidate();
            }
        }


        private void rectangleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = true;
            IsShapeCircle = false;
            IsShapeLine = false; 
        }

        private void ellipseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeRectangle = false;
            IsShapeCircle = true;
            IsShapeLine = false; 
        }

        private void lineToolStripMenuItem_Click(object sender, EventArgs e)
        {
            IsShapeCircle = false;
            IsShapeRectangle = false;
            IsShapeLine = true; 
        }

        private void ThicknessLevel0_Click(object sender, EventArgs e)
        {
            brushThickness = 0; 
        }

        private void ThicknessLevel2_Click(object sender, EventArgs e)
        {
            brushThickness = 2; 
        }

        private void ThicknessLevel4_Click(object sender, EventArgs e)
        {
            brushThickness = 4; 
        }

        private void ThicknessLevel6_Click(object sender, EventArgs e)
        {
            brushThickness = 6; 
        }

        private void ThicknessLevel8_Click(object sender, EventArgs e)
        {
            brushThickness = 8; 
        }

        private void ThicknessLevel10_Click(object sender, EventArgs e)
        {
            brushThickness = 10; 
        }

        private void ThicknessLevel12_Click(object sender, EventArgs e)
        {
            brushThickness = 12; 
        }

        private void ThicknessLevel14_Click(object sender, EventArgs e)
        {
            brushThickness = 14; 
        }

        private void FillColour_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }

        private void button1_Click(object sender, EventArgs e)
        {

            ColorDialog fillColourDialog = new ColorDialog();
            fillColourDialog.ShowDialog();
            currentBoarderColor = fillColourDialog.Color;
            panelArea.Invalidate(); 
        }


    }
}

Wie höre ich das Flackern auf?

* UPDATE: * Dieser Code funktioniert gut, wenn ich direkt auf dem Formular zeichne. Wenn ich jedoch versuche, auf das Panel zu zeichnen, wird das Flackern zum Problem

61
BigBug

Schließlich löste sich das Flimmern. Da ich auf einem Panel anstelle des Formulars gezeichnet habe, löst die folgende Codezeile das Flackern nicht: 

this.SetStyle(
    ControlStyles.AllPaintingInWmPaint | 
    ControlStyles.UserPaint | 
    ControlStyles.DoubleBuffer, 
    true);

SetStyle muss vom Typ 'YourProject.YourProject' sein (oder von diesem abgeleitet sein). Daher müssen Sie eine Klasse als solche erstellen (damit Sie MyPanel verwenden können, das von SPaint.SPaint abgeleitet ist und daher die direkte Pufferung verwenden kann das Panel - und nicht das Formular):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SPaint; 

namespace YourProject
{
    public class MyPanel : System.Windows.Forms.Panel
    {
        public MyPanel()
        {
            this.SetStyle(
                System.Windows.Forms.ControlStyles.UserPaint | 
                System.Windows.Forms.ControlStyles.AllPaintingInWmPaint | 
                System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, 
                true);
        }
    }
}

Nachdem Sie dies getan haben (obwohl Sie eigentlich nie den Designer-Code bearbeiten sollten, wenn Sie nicht wirklich wissen, was Sie tun), müssen Sie Form.Designer.cs bearbeiten. In dieser Datei finden Sie Code, der folgendermaßen aussieht:

this.panelArea = new YourProject.MyPanel();

Die obige Zeile muss geändert werden in:

this.panelArea = new MyPanel(); 

Nachdem ich diese Schritte ausgeführt habe, flackert mein Paint-Programm nicht mehr. 

Für alle anderen, die das gleiche Problem haben, ist das Problem endgültig gelöst. 

Genießen!

52
BigBug

Für eine "sauberere Lösung" und um das Basispanel weiterhin verwenden zu können, können Sie Reflection einfach zum Implementieren der doppelten Pufferung verwenden, indem Sie diesen Code dem Formular hinzufügen, das die Panels enthält, in denen Sie zeichnen möchten

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

Wobei "DrawingPanel" der Name des Bereichs ist, in dem die doppelte Pufferung erfolgen soll.

Ich weiß, dass viel Zeit vergangen ist, seit die Frage gestellt wurde, aber dies könnte in der Zukunft jemandem helfen.

54
viper

Kopieren Sie diese und fügen Sie sie in Ihr Projekt ein

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

Dies ermöglicht die Doppelpufferung für alle Steuerelemente von der Formularebene nach unten. Andernfalls muss die Doppelpufferung für jede Option einzeln aktiviert werden. Möglicherweise möchten Sie den Doppelpufferring anschließend fein abstimmen, da die Doppelpufferung möglicherweise unerwünschte Nebenwirkungen verursacht.

26
user3641393

Ich hatte das gleiche Problem. Ich konnte mich nie zu 100% von dem Flimmern befreien (siehe Punkt 2), aber ich habe dies verwendet

protected override void OnPaint(PaintEventArgs e) {}

ebenso gut wie

this.DoubleBuffered = true;

Das Hauptproblem beim Flimmern ist, dass Sie sicher gehen

  1. Malen Sie die Dinge in der richtigen Reihenfolge!
  2. stellen Sie sicher, dass Ihre Zeichnungsfunktion <etwa 1/60 Sekunde ist

winforms ruft die Methode OnPaint jedes Mal auf, wenn das Formular neu gezeichnet werden muss. Es gibt viele Möglichkeiten, die Devalidierung zu ermöglichen. Wenn Sie beispielsweise den Mauszeiger über das Formular bewegen, kann es manchmal zu einem Neuzeichnen-Ereignis kommen.

Ein wichtiger Hinweis zu OnPaint ist, dass Sie nicht jedes Mal bei Null anfangen, sondern stattdessen von dem Punkt beginnen, an dem Sie sich befanden. Wenn Sie die Hintergrundfarbe überfluten, werden Sie wahrscheinlich flackern.

Zum Schluss dein GFX-Objekt. In OnPaint müssen Sie das Grafikobjekt neu erstellen, jedoch NUR, wenn sich die Bildschirmgröße geändert hat. Das Wiederherstellen des Objekts ist sehr teuer und muss vor der Neuerstellung entsorgt werden (die Garbage Collection behandelt das Objekt nicht zu 100% richtig oder sagt die Dokumentation). Ich habe eine Klassenvariable erstellt

protected Graphics gfx = null;

und benutzte es dann lokal in OnPaint, aber das lag daran, dass ich das gfx-Objekt an anderen Stellen in meiner Klasse verwenden musste. Andernfalls tun Sie dies nicht. Wenn Sie nur in OnPaint malen, verwenden Sie bitte e.Graphics !!

// clean up old graphics object
gfx.Dispose();

// recreate graphics object (dont use e.Graphics, because we need to use it 
// in other functions)
gfx = this.CreateGraphics();

Hoffe das hilft.

14
ohmusama

Ich weiß, das ist eine wirklich alte Frage, aber vielleicht wird sie jemand nützlich finden.
Ich möchte die Antwort von Viper etwas verbessern.

Sie können eine einfache Erweiterung für die Panel-Klasse vornehmen und die Einstellungseigenschaft durch Spiegelung ausblenden.

public static class MyExtensions {

    public static void SetDoubleBuffered(this Panel panel) {
        typeof(Panel).InvokeMember(
           "DoubleBuffered",
           BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty,
           null,
           panel,
           new object[] { true });
    }
}

Wenn der Name Ihrer Panel-Variablen myPanel ist, können Sie einfach anrufen
myPanel.SetDoubleBuffered ();
und das ist es. Code sieht viel sauberer aus.

3
thorgil

Doppelpufferung wird hier leider keine große Hilfe sein. Ich bin vor einiger Zeit darauf gestoßen und habe am Ende ein eigenes Panel auf ziemlich unbeholfene Weise hinzugefügt, aber es funktionierte für meine Anwendung. 

Machen Sie aus dem ursprünglichen Panel (panelArea) einen transparenten Bereich und legen Sie es auf ein zweites Panel, das Sie beispielsweise panelDraw nennen. Stellen Sie sicher, dass Sie panelArea vorne haben. Ich peitschte das auf und es wurde das Flackern entfernt, aber die gezeichnete Form wurde verschmiert, so dass es auch keine vollständige Lösung ist.

Ein transparentes Bedienfeld kann durch Überschreiben einiger Paint-Aktionen des ursprünglichen Bedienfelds erstellt werden:

public class ClearPanel : Panel
{
    public ClearPanel(){}

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams createParams = base.CreateParams;
            createParams.ExStyle |= 0x00000020;
            return createParams;
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e){}
}

Die Idee ist, die temporäre Form während des MouseMove-Ereignisses von 'panelArea' zu zeichnen und NUR die 'panelDraw' im MouseUp-Ereignis NUR neu zu zeichnen. 

// Use the panelDraw Paint event to draw shapes that are done
void panelDraw_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelDraw.CreateGraphics();

    foreach (Rectangle shape in listOfShapes)
    {
        shape.Draw(g);
    }
}

// Use the panelArea_Paint event to update the new shape-dragging...
private void panelArea_Paint(object sender, PaintEventArgs e)
{
    Graphics g = panelArea.CreateGraphics();

    if (drawSETPaint == true)
    {
        Pen p = new Pen(Color.Blue);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dash;

        if (IsShapeRectangle == true)
        {
            g.DrawRectangle(p, rect);
        }
        else if (IsShapeCircle == true)
        {
            g.DrawEllipse(p, rect);
        }
        else if (IsShapeLine == true)
        {
            g.DrawLine(p, startPoint, endPoint);
        }
    }
}

private void panelArea_MouseUp(object sender, MouseEventArgs e)
{

    endPoint.X = e.X;
    endPoint.Y = e.Y;

    drawSETPaint = false;

    if (rect.Width > 0 && rect.Height > 0)
    {
        if (IsShapeRectangle == true)
        {
            listOfShapes.Add(new TheRectangles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeCircle == true)
        {
            listOfShapes.Add(new TheCircles(rect, currentColor, currentBoarderColor, brushThickness));
        }
        else if (IsShapeLine == true)
        {
            listOfShapes.Add(new TheLines(startPoint, endPoint, currentColor, currentBoarderColor, brushThickness));
        }

        panelArea.Invalidate();
    }

    panelDraw.Invalidate();
}
3
Tom

In dieser Bedingung müssen Sie den Doppelpuffer aktivieren Öffnen Sie das aktuelle Formular und gehen Sie zu den Formulareigenschaften und wenden Sie den Doppelpuffer an. 

this.DoubleBuffered = true;     

In Form laden.

2
user2922221

Ich würde raten, OnPaintBackground zu überschreiben und den Hintergrund selbst zu löschen. Wenn Sie wissen, dass Sie das gesamte Steuerelement zeichnen, können Sie in OnPaintBackground einfach nichts tun (nennen Sie nicht die Basismethode), und die Hintergrundfarbe wird nicht zuerst gezeichnet

2
Matt

wenn all dies nicht funktioniert, können Sie jederzeit einen eigenen Doppelpuffer-Link zum Microsoft-Lernprogramm erstellen: https://docs.Microsoft.com/en-us/dotnet/framework/winforms/advanced/how- Grafikflimmern mit doppelter Pufferung für Formulare und Steuerelemente reduzieren

hofft, dass es für Sie funktioniert

1

hier ist das Programm der Bewegung des Kreises in .net, das nicht flackert.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
namespace CircleMove
{
    /// <summary>
    /// Description of MainForm.
    /// </summary>
    public partial class MainForm : Form
    {
        int x=0,y=0;
        Thread t;

        public MainForm()
        {

            //
            // The InitializeComponent() call is required for Windows Forms designer support.
            //
            InitializeComponent();

            //
            // TODO: Add constructor code after the InitializeComponent() call.
            //
        }
        void MainFormPaint(object sender, PaintEventArgs e)
        {
            Graphics g=e.Graphics;
            Pen p=new Pen(Color.Orange);
            Brush b=new SolidBrush(Color.Red);
        //  g.FillRectangle(b,0,0,100,100);
            g.FillEllipse(b,x,y,100,100);
        }
        void MainFormLoad(object sender, EventArgs e)
        {
            t=new Thread(  new ThreadStart(

                ()=>{
                    while(true)
                    {
                        Thread.Sleep(10);
                        x++;y++;
                        this.Invoke(new Action(
                            ()=>{

                                this.Refresh();
                                this.Invalidate();
                                this.DoubleBuffered=true;
                                }
                                            )
                                        );
                    }
                    }
                                            )

                        );

            t.Start();
        }
    }
}
1
sanjay

Versuchen Sie, die Zeichnungslogik in das aktuelle Formular einzufügen 

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
}

methode. In diesem Fall sollten Sie den Parameter e verwenden, um das Graphics-Objekt abzurufen. Verwenden Sie die Eigenschaft e.Graphics. Dann sollten Sie die Invalidate () - Methode für dieses Formular immer dann aufrufen, wenn das Formular neu gezeichnet werden muss . PS: DoubleBuffered muss auf true gesetzt sein.

0
DeeDee

Wenn der Speicher knapp ist (Sie möchten also nicht die Speicherkosten von Doppelpuffern), besteht eine Möglichkeit, das Flimmern zu reduzieren, nicht zu beseitigen, indem Sie die Hintergrundfarbe in der aktuellen Szene auf die dominierende Farbe setzen.

Warum dies hilfreich ist: Flimmern ist ein kurzes Aufblitzen der Hintergrundfarbe, die das Betriebssystem zeichnet, bevor untergeordnete Steuerelemente oder der benutzerdefinierte Zeichencode gezeichnet werden. Wenn es sich bei diesem Blitz um eine Farbe handelt, die näher an der anzuzeigenden Endfarbe liegt, ist dies weniger wahrnehmbar.

Wenn Sie sich nicht sicher sind, mit welcher Farbe Sie beginnen möchten, beginnen Sie mit 50% Grau, da dies ein Durchschnitt aus Schwarz und Weiß ist, also näher an den meisten Farben in Ihrer Szene ist.

myFormOrControl.BackColor = Color.Gray;
0
ToolmakerSteve

tun Sie einfach this.Refresh(), wenn das Formular angezeigt wird. 

0
Jam