Autor Beitrag
mvollmer
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 42
Erhaltene Danke: 3


Delphi (Seattle), C (MPLAB X)
BeitragVerfasst: Do 24.02.11 17:37 
Guten Tag,

ich bin grade dabei ein Tetris zu programmieren.
Das spielfeld ist ein Panel. Bekanntlicher weise, muss man ja DoubleBuffered auf true setzten.
Doch das reicht immer noch nicht aus und es flackert immer noch.
Zusätzlich habe ich diese Styles gesetzt
ausblenden C#-Quelltext
1:
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer ,true);					


Hier mal mein Quelltext :

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
public class MyPanel : System.Windows.Forms.Panel
    {
        public MyPanel()
        {
            this.DoubleBuffered = true;
        }
    }

    public partial class MainForm : Form
    {
        public MainForm()
        {
            GameField = new MyPanel();
            InitializeComponent();
            this.DoubleBuffered = true;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer ,true);
            this.UpdateStyles();
            timer1.Interval = 156;
        }

Die Aktualisierungsrate des Panels beträgt die Interval von timer1
Gibt es eventuell eine weitere Lösung?

Mit freundlichen Grüßen
mvollmer
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 24.02.11 18:12 
Hallo,

durch "DoubleBuffered=true" werden schon die internen Styles gesetzt (laut Reflector):
ausblenden C#-Quelltext
1:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, value);					


Gibt es denn einen Unterschied zu "DoubleBuffered=false"?

Ein kleines Flackern kannst du bei Bewegungen mit WinForms nicht vermeiden (habe ich bei meinem Rennspiel FastDriver auch), jedoch sollten fixe Objekte (z.B. Texte) flimmerfrei dargestellt werden.

Evtl. kannst du dir noch Anregungen aus Flackernde Controls vermeiden / Schnelles, flackerfreies Zeichnen holen...

P.S: Wie bist du gerade auf 156 gekommen (Bedenke, daß ein Windows-Timer nur eine Genauigkeit von 14-15ms hat).


Zuletzt bearbeitet von Th69 am Do 24.02.11 18:17, insgesamt 1-mal bearbeitet
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 24.02.11 18:14 
Ich vermute, dass du erstens direkt auf das Panel einzeln zeichnest und zweitens den kompletten Hintergrund zuerst löschst?

Durch das Löschen flackert es dann kurz. Wenn du alles zuerst zusammenbaust und dann komplett auf das Panel kopierst, wird es vermutlich weniger flackern.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 24.02.11 18:20 
Hallo jaenicke,

durch das DoubleBuffered (OptimizedDoubleBuffer) wird ja intern schon eine Bitmap verwendet, die dann direkt 'geblitted' wird.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19272
Erhaltene Danke: 1740

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Do 24.02.11 18:33 
Das bringt aber nicht viel, wenn man zuerst die ganze Zeichenfläche löscht und die Elemente dann einzeln draufzeichnet. Denn das werden dann offenbar auch mehrere Kopiervorgänge. Erst ab Vista nimmt auch dann das Flackern durch das interne Zwischenspeichern von Vista/7 ab.

Wenn man natürlich nur selektiv Teile der Fläche löscht, kann das schon reichen, aber wenn man alles löscht, hat es bei mir trotz DoubleBuffered geflackert, insbesondere unter XP.
mvollmer Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 42
Erhaltene Danke: 3


Delphi (Seattle), C (MPLAB X)
BeitragVerfasst: Fr 25.02.11 09:11 
Hallo Th69,
Hallo jaenicke,

danke erstmal für eure Beteiligung :)

Zitat:
Gibt es denn einen Unterschied zu "DoubleBuffered=false"?

Habs grade mal ausprobiert. Macht keinen unterschied. Daraus lässt sich ja erkennen, das es schonmal damit nichts zu tun hat.

Zitat:
P.S: Wie bist du gerade auf 156 gekommen (Bedenke, daß ein Windows-Timer nur eine Genauigkeit von 14-15ms hat).

140ms zum Zeichnen + 16 ms ungenauigkeit.

Zitat:
Ich vermute, dass du erstens direkt auf das Panel einzeln zeichnest und zweitens den kompletten Hintergrund zuerst löschst?

Nein, ich erstelle eine neue Bitmap, zeichne dort alles drauf und zum Schluss setzte ich per BackgroundImage die Bitmap.

Ich poste mal meine Zeichenprozedure, vielleicht findet ihr etwas, was dazu führen könnte.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
public void drawPlayingField()
        {
            Bitmap pf = new Bitmap(_SizeX * bitmapSize, _SizeY * bitmapSize);
            Graphics g = Graphics.FromImage(pf);
            Point p = new Point();
            Image imgEmpty = Image.FromFile("Texture/empty.jpg");
            for (int i = 0; i < _SizeY; i++)
            {
                for (int k = 0; k < _SizeX; k++)
                {
                    p.X = bitmapSize * k;
                    p.Y = bitmapSize * i;
                    switch (PlayField[k, i].state)
                    {
                        case Field.FieldState.Emtpy:
                            drawField(g, p, imgEmpty);
                            break;
                        case Field.FieldState.Blocked:
                            // TODO
                            break;
                        case Field.FieldState.Brick:
                            drawField(g, p, PlayField[k, i].brick.texture);
                            break;
                        default:
                            break;
                    }
                }
            }
            field.BackgroundImage = pf;
}

private void drawField(Graphics g, Point p, Image img)
        {
            Pen GrayPen = new Pen(Color.Gray, 1);
            Rectangle rect = new Rectangle(p.X, p.Y, bitmapSize, bitmapSize);
            g.DrawImageUnscaledAndClipped(img, rect);
            g.DrawRectangle(GrayPen, rect);
        }
mvollmer Threadstarter
ontopic starontopic starontopic starontopic starontopic starofftopic starofftopic starofftopic star
Beiträge: 42
Erhaltene Danke: 3


Delphi (Seattle), C (MPLAB X)
BeitragVerfasst: Fr 25.02.11 12:14 
Guten Tag,

ich weiß nicht warum. Aber als Spielfeld hab ich ein Panel genommen. Aufjedenfall hab ich das Panel jetzt durch eine PictureBox ersetzt und alles klappt wunderbar :)
VampireSilence
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 109
Erhaltene Danke: 5


C# (VS 2008 Express), PHP/MySQL, Windows XP
BeitragVerfasst: Sa 25.06.11 12:38 
Das kann ich dir sagen. In dem Moment, wo du das Bild mit
ausblenden C#-Quelltext
1:
field.BackgroundImage = pf;					

änderst, veränderst du nicht nur das aktuell dargestellte Bild, sondern du veränderst auch den Quellverweis von field.BackgroundImage -> auf pf.

Ein Beispiel: Würdest du pf jetzt wieder ändern, würdest du diese Veränderung sofort sehen, weil BackgroundImage nun pf als Quelle verwendet und ständig aktuell hält. Eine Zuweisung ist also auch nur ein einziges Mal nötig.

The Better Way:
1. Nur ein Verweis (am besten direkt in der construct()-Methode, unter InitializeComponent())

2.a) Entweder du clonst dein Bild einfach in den Objektverweis hinein (einmaliges initialisieren des BackgroundImage-Bitmaps erforderlich), mit
ausblenden C#-Quelltext
1:
field.BackgroundImage = (Bitmap)pf.Clone();					

(hierbei ist es aber wiederrum ein wildes rumgecaste, das Zeit kostet) oder noch besser:

2.b) Du erstellst dir deinen eigenen Buffer und zeichnest diesen nur noch (opaque!) über den aktuellen Frame, in etwa so:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
namespace AnyNS(musst du noch anpassen)
{
    public partial class frmMain(oder wie immer deine Form heisst) : Form
    {
        Bitmap _Buffer, _Frame;
        Graphics _GFX, _Translator;

        public frmMain()
        {
            InitializeComponent();
            //...
            _Buffer = new Bitmap(höhe, breite),
                _Frame = new Bitmap(höhe, breite);
            _GFX = Graphics.FromImage(_Buffer),  //  Mit dieser _GFX wird nun jedes Draw() ausgeführt, dass den Frame zusammensetzt !
                _Translator = Graphics.FromImage(_Frame);
            field.BackgroundImage = Frame;  //  Die einzige(!) Zuweisung auf den Frame
        }
    }

Du erstellt also einen Buffer, den du beschreibst. Dann einen Frame, der immer das aktuell angezeige Bild repräsentiert und wichtig: Keines der Objekte wird ständig komplett neu erstellt, das kostet nämlich Zeit (Zeit, in der der Bildschirm kurzzeitig weiß ist !). Wenn der Frame fertig ist, Drawst du ihn mit dem Translator ganz einfach in den Frame und das geht rasent schnell und erzeugt keinen einzigen Whiteflash:
ausblenden C#-Quelltext
1:
2:
3:
field.BackgroundImage = pf;
// ersetzen durch:
_Translator.DrawImage(_Buffer, new Rectangle(Point.Empty, _Buffer.Size), new Rectangle(Point.Empty, _Frame.Size), GraphicsUnit.Pixel);

Durch die Rectangle->Rectangle-Methode kommst du im übrigen niemals in die Verlegenheit von überstehenden Kanten etc., weil die Bildabmessungen automatisch aneinander angeglichen werden. Trotzdem sollten die natürlich gleich groß sein, sonst sieht das Bild später verzerrt aus. ;)

mfg
- VampireSilence