views:

173

answers:

3

I have written the following simple program, which draws lines on the screen every 100 milliseconds (triggered by timer1). I noticed that the drawing flickers a bit (that is, the window is not always completely blue, but some gray shines through). So my idea was to use double-buffering. But when I did that, it made things even worse. Now the screen was almost always gray, and only occasionally did the blue color come through (demonstrated by timer2, switching the DoubleBuffered property every 2000 milliseconds).

What could be an explanation for this?

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApplication1 {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Paint(object sender, PaintEventArgs e) {
            Graphics g = CreateGraphics();
            Pen pen = new Pen(Color.Blue, 1.0f);
            Random rnd = new Random();
            for (int i = 0; i < Height; i++)
                g.DrawLine(pen, 0, i, Width, i);
        }

        // every 100 ms
        private void timer1_Tick(object sender, EventArgs e) {
            Invalidate();
        }

        // every 2000 ms
        private void timer2_Tick(object sender, EventArgs e) {
            DoubleBuffered = !DoubleBuffered;
            this.Text = DoubleBuffered ? "yes" : "no";
        }
    }
}
+1  A: 

Try setting the double buffered property to true just once in the constructor while you're testing.

You need to make use of the back buffer. Try this:

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 DoubleBufferTest
{
    public partial class Form1 : Form
    {
        private BufferedGraphicsContext context;
        private BufferedGraphics grafx;

        public Form1()
        {
            InitializeComponent();

            this.Resize += new EventHandler(this.OnResize);
            DoubleBuffered = true;

            // Retrieves the BufferedGraphicsContext for the 
            // current application domain.
            context = BufferedGraphicsManager.Current;

            UpdateBuffer();
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            this.Refresh();

        }

        private void OnResize(object sender, EventArgs e)
        {
            UpdateBuffer();
            this.Refresh();
        }

        private void UpdateBuffer()
        {
            // Sets the maximum size for the primary graphics buffer
            // of the buffered graphics context for the application
            // domain.  Any allocation requests for a buffer larger 
            // than this will create a temporary buffered graphics 
            // context to host the graphics buffer.
            context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);

            // Allocates a graphics buffer the size of this form
            // using the pixel format of the Graphics created by 
            // the Form.CreateGraphics() method, which returns a 
            // Graphics object that matches the pixel format of the form.
            grafx = context.Allocate(this.CreateGraphics(),
                 new Rectangle(0, 0, this.Width, this.Height));

            // Draw the first frame to the buffer.
            DrawToBuffer(grafx.Graphics);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            grafx.Render(e.Graphics);
        }

        private void DrawToBuffer(Graphics g)
        {
            //Graphics g = grafx.Graphics;
            Pen pen = new Pen(Color.Blue, 1.0f);
            //Random rnd = new Random();
            for (int i = 0; i < Height; i++)
                g.DrawLine(pen, 0, i, Width, i);
        }
    }
}

It's a slightly hacked around version of a double buffering example on MSDN.

Jon Cage
I already did that, with the same effect. The periodic switching is just for showing the effect more clearly.
Roland Illig
+4  A: 

I would just draw all of your items to your own buffer, then copy it all in at once. I've used this for graphics in many applications, and it has always worked very well for me:

    public Form1()
    {
        InitializeComponent();
    }
    private void timer1_Tick(object sender, EventArgs e)
    {
        Invalidate();// every 100 ms
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        DoubleBuffered = true;
    }
    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        Bitmap buffer = new Bitmap(Width, Height);
        Graphics g = Graphics.FromImage(buffer);
        Pen pen = new Pen(Color.Blue, 1.0f);
        //Random rnd = new Random();
        for (int i = 0; i < Height; i++)
            g.DrawLine(pen, 0, i, Width, i);
        BackgroundImage = buffer;
    }

EDIT: After further investigation, it looks like your problem is what you're setting your Graphics object to:

Graphics g = CreateGraphics();

needs to be:

Graphics g = e.Graphics();

So your problem can be solved by either creating a manual buffer like I did above, or simply changing you Graphics object. I've tested both and they both work.

smoore
+2  A: 

No need to use multiple buffers or Bitmap objects or anything.

Why don't you use the Graphics object provided by the Paint event? Like this:

private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Pen pen = new Pen(Color.Blue, 1.0f);
    Random rnd = new Random();
    for (int i = 0; i < Height; i++)
        g.DrawLine(pen, 0, i, Width, i);
}
Virtlink
Yes, you are correct. That was part 2 of my answer.
smoore
It's simple and it works. Thanks.
Roland Illig