Laden...

Das Zeichnen von vielen kleinen Kacheln flackert

Erstellt von trib vor 11 Jahren Letzter Beitrag vor 11 Jahren 2.200 Views
Hinweis von herbivore vor 11 Jahren

Abgeteilt von Matrix in Schlangenlinien befüllen

T
trib Themenstarter:in
708 Beiträge seit 2008
vor 11 Jahren

Mahlzeit,

ich habe fleißig weiter programmiert und das Grund-Control der Matrix soweit fertig.
Nun kann man mit einer Handvoll Funktionen verschiedene Muster darstellen und hübsch animieren.

Damit ist mir nun aufgefallen, dass ein gezeichneter Kreis im Gegensatz zu einem Rechteck schon ziemlich flackert. Lege ich nun noch (wie im Screenshot oben zu sehen) die Pixel Nummer darüber, kann man schon fast jeden Frame mitzählen!

Der DoubleBuffer ist schon aktiviert und ich zeichne auch nur die nötigen Pixel neu.

 privat private void DrawPixel(int pixelNo)
        {
            Point p = GetPixelLocation(pixelNo);
            int x = p.X;
            int y = p.Y;
            
            string text = string.Empty;
            Rectangle myRectangle = new Rectangle((this.Padding.Left + pixelWidth + this.Padding.Right) * x,
                                                  (this.Padding.Top + pixelHeight + this.Padding.Bottom) * y,
                                                  pixelWidth, pixelHeight);
            if (DrawPixelNo)
                text = pixelNo.ToString();

            if (listGo.Count <= pixelNo)
            {
                if (this.PixelStyle == PixelStyles.Circle)
                    listGo.Add(new MyFilledEllipse(new SolidBrush(ledPixels.LedStrip.Chain[pixelNo].RGBColor.ToColor()),
                                                    myRectangle, text));
                else if (this.PixelStyle == PixelStyles.Rectangel)
                    listGo.Add(new MyFilledRectangle(new SolidBrush(ledPixels.LedStrip.Chain[pixelNo].RGBColor.ToColor()),
                                                      myRectangle, text));
            }
            else
            {
                if (this.PixelStyle == PixelStyles.Circle)
                    listGo[pixelNo] = new MyFilledEllipse(new SolidBrush(ledPixels.LedStrip.Chain[pixelNo].RGBColor.ToColor()),
                                                    myRectangle, text);
                else if (this.PixelStyle == PixelStyles.Rectangel)
                    listGo[pixelNo] = new MyFilledRectangle(new SolidBrush(ledPixels.LedStrip.Chain[pixelNo].RGBColor.ToColor()),
                                                      myRectangle, text);
                this.Invalidate(myRectangle);
            }
        }

Oder liegt der Hund beim OnPaint begraben, dort wird natürlich immer alles wieder neu gezeichnet.
Macht es vielleicht Sinn eine Logik zu implementieren, die sich merkt welche Pixel geändert wurden und nur diese zu aktualisieren? Quasi eine Black- & WhiteList. Nur wenn z.B. die Größe oder Art der Pixel geändert wird, gibt es ein Clear().


        protected override void OnPaint(PaintEventArgs e)
        {
            if (currentGridSize != this.Size)
                CreatePixels();
            e.Graphics.Clear(this.BackColor);
            base.OnPaint(e);

            foreach (MyGraphicObject go in listGo)
            {
                go.Draw(e.Graphics);
            }
        }

(Der aufmerksame Betrachter wird merken wo ich das her habe 😃 )

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo trib,

sicher ist es von Vorteil, wenn nur die tatsächlich veränderten Bereiche neu gezeichnet werden. Das ist entsprechend einer der Vorschläge, die in [FAQ] Flackernde Controls vermeiden / Schnelles, flackerfreies Zeichnen und den verlinkten Threads gemacht werden. Du solltest zunächst alle Tipps ausprobieren.

herbivore

T
trib Themenstarter:in
708 Beiträge seit 2008
vor 11 Jahren

Hallo herbivore,

ich bin schon durch einige Threads gegangen und habe festgestellt, dass in den Beispielen ebenfalls im OnPaint per foreach alle GraphicsObjects durchgegangen werden.
Der Unterschied besteht nur darin, dass die Liste mit den GraphicsObjects nur aktualisiert wird, wo es nötig ist. So mache ich das ja bereits.

Zeige ich 20x20 Rechtecke an und aktualisiere alle 50Millisekunden eine Reihe aus 20 Stück, so funktioniert das hervorragend. Ändere ich nun die Darstellung auf einen Kreis, Flackert immer die entsprechende Reihe. Der Rest wird ja nicht neu gerendert.
Das wird letztendlich mit dem Kreis an sich zu tun haben.

Was mir aber aufgefallen ist, ist das

this.Invalidate(myRectangle);

Damit aktualisiere ich immer sofort jeden aktualisierten Pixel. Wenn ich nun z.B. die gesamte erste Reihe aktualisieren möchte, macht es mehr Sinn den Tipp aus "Gezieltes OwnerDrawing" - schnelles Zeichnen bewegter Objekte zu beherzigen und das Invalidate auf den geänderten Bereich anzuwenden.

Auf den ersten Blick tut das schon genau was ich brauche. Nun ist es am Feintuning die Bereiche Sinnvoll aufzuteilen.
Entweder per fester Frequenz oder nach logischer Aufteilung (Pro Spalte, Bereich, usw.)
Das würde aber bei dargestellten Quadraten (nicht ausgefüllt) mehr aktualisieren als nötig. Zumindest aber nicht so häufig 😃

Danke für den Tipp und Entschuldigung, dass ich nicht gleich die anderen Threads gelesen habe. Ich wollte erstmal mein Problem überhaupt eingrenzen um zu wissen wonach ich suchen muss.

Allen ein schönes Wochenende!

T
trib Themenstarter:in
708 Beiträge seit 2008
vor 11 Jahren

Da muss ich doch glatt meinen Thread wieder aus der Versenkung holen.

Nachdem ich seit längerer Zeit mein Projekt habe brach liegen lassen, hat mich mal wieder der Ehrgeiz gepackt.
Die Problematik ist weiterhin folgende gewesen:
Zeichne ich 100 "Pixel" mit DrawRectangle, so werden diese nahezu sofortig angezeigt.
Nutze ich selbige Logik für Kreise mit DrawEllipse, dauert dies doppelt so lange.
Die Dauer steigt exponentiell mit der Anzahl der "Pixel".

Sobald sich die Farbe oder Größe eines Pixels ändert, so wird nur dessen Bereich neu gezeichnet. Quasi alle Tipps in den o.g. Links habe ich ausprobiert und die Zeiten miteinander verglichen, die das Zeichen von 100 Kreisen beansprucht hat. Ohne eine wirkliche Verbesserung zu spüren.

Nun habe ich das Projekt neu aufgebaut und gesehen, dass ich zwar in der Hauptform

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

setze, nicht aber in dem Control selbst. Und genau da lag der Hase begraben!
Naiver Weise bin ich davon ausgegangen, dass das Setzen des DoubleBuffers auf der Form sich automatisch auf dessen Controls auswirkt. Das ist aber nicht so!

Die ganze Testerei war also sinnlos und der DoubleBuffer im Konstruktor des Controls hat das gewünschte Ergebnis erzielt.

Vielleicht hilft diese späte Erkenntnis jemandem weiter 😃

5.658 Beiträge seit 2006
vor 11 Jahren

Hi trib,

in herbivores Artikel steht gleich als erstes:

Wenn es beim (Selber-)Zeichnen flackert, probiert beim flackernden Control.DoubleBuffered = true zu setzen.

Christian

Weeks of programming can save you hours of planning

49.485 Beiträge seit 2005
vor 11 Jahren

Hallo MrSparkle,

allerdings stand "Control" ohne Hervorhebung da. Jetzt habe ich "Control" hervorgehoben.

Früher stand in [Artikel] Flackernde Controls und flackerndes Zeichnen vermeiden nur:

wenn die "Form" DoubleBuffered ist, Flackern die Controls auch weiterhin, da ausschließlich die Form buffert, nicht aber die Controls.

Der folgende Satz wurde ebenfalls erst später hinzugefügt:

DoubleBuffered muss also auch für alle flackernden Controls gesetzt werden.

Insofern sind solche Feedbacks schon nützlich, um die FAQ und die Artikel im Laufe der Zeit immer besser zu machen. Wie sicher jeder selber aus eigener Erfahrung kennt, ist offensichtliches manchmal nicht offensichtlich genug.

herbivore