5
votes

I'm trying to implement the following method: void Ball::DrawOn(Graphics g);

The method should draw all previous locations(stored in a queue) of the ball and finally the current location. I don't know if that matters, but I print the previous locations using g.DrawEllipse(...) and the current location using g.FillEllipse(...).

The question is, that as you could imagine there is a lot of drawing to be done and thus the display starts to flicker much. I had searched for a way to double buffer, but all I could find is these 2 ways:

1) System.Windows.Forms.Control.DoubleBuffered = true;

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

while trying to use the first, I get the an error explaining that from in this method the Property DoubleBuffered is inaccessible due to its protection level. While I can't figure how to use the SetStyle method.

Is it possible at all to double buffer while all the access I have is to the Graphics Object I get as input in the method?

Thanks in Advance,

Edit: I had created the following class

namespace doubleBuffer
{
    class BufferedBall : System.Windows.Forms.Form{
        private Ball ball;
        public BufferedBall(Ball ball)
        {
            this.ball = ball;
        }

    public void DrawOn(Graphics g){
        this.DoubleBuffered = true;
        int num = 0;
        Rectangle drawArea1 = new Rectangle(5, 35, 30, 100);
        LinearGradientBrush linearBrush1 =
        new LinearGradientBrush(drawArea1, Color.Green, Color.Orange, LinearGradientMode.Horizontal);
        Rectangle drawArea2 = new Rectangle(5, 35, 30, 100);
        LinearGradientBrush linearBrush2 =
           new LinearGradientBrush(drawArea2, Color.Black, Color.Red, LinearGradientMode.Vertical);
        foreach (PointD point in ball.previousLocations)
        {
            Pen myPen1;
            if (num % 3 == 0)
                myPen1 = new Pen(Color.Yellow, 1F);
            else if (num % 3 == 1)
                myPen1 = new Pen(Color.Green, 2F);
            else
                myPen1 = new Pen(Color.Red, 3F);
            num++;
            myPen1.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
            myPen1.StartCap = System.Drawing.Drawing2D.LineCap.RoundAnchor;
            myPen1.EndCap = System.Drawing.Drawing2D.LineCap.AnchorMask;
            g.DrawEllipse(myPen1, (float)(point.X - ball.radius), (float)(point.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
        }
        if ((ball.Host.ElapsedTime * ball.Host.FPS * 10) % 2 == 0){
            g.FillEllipse(linearBrush1, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
        }else{
            g.FillEllipse(linearBrush2, (float)(ball.Location.X - ball.radius), (float)(ball.Location.Y + ball.radius), (float)(2 * ball.radius), (float)(2 * ball.radius));
        }
    }
}

}

and the ball drawOn looks like this:

new BufferedBall(this).DrawOn(g);

Is that what you meant? because it is still flickering?

5
Well, the SetStyle() way is correct, and you're doing it correct. AFAIK, the DoubleBuffered property shouldn't be used (SetStyle is preferred), but you can always subclass the Control/Form/whatever and use it there (it's protected). - sunside

5 Answers

3
votes

Form class has DoubleBuffered property exposed as protected. http://msdn.microsoft.com/en-us/library/system.windows.forms.control.doublebuffered.aspx but since you derive your form from Form you can use it.

3
votes
this.DoubleBuffered = true;

That's fine but you have to move this statement to the constructor. Double-buffering requires setup, Windows Forms has to create a buffer, that must be done before the paint event runs. The constructor is ideal.

2
votes

A simpler way to set style for double buffering of Control-derived classes is to use reflection. See here: http://www.csharp-examples.net/set-doublebuffered/

That would save you the step of subclassing a control just to set a protected property.

1
votes

You don't need to set DoubleBuffered to true each time you redraw. It is not disabled when drawing finished. Just remove the line from DrawOn and set it in the constructor or Forms Designer and check the results. Setting the value to false produces significant flickering while setting to true doesn't.

I tried your code in a form where a timer forces a redraw every millisecond and noticed no flickering when DoubleBuffered is true:

private int yDir = 1,xDir=1;
int step = 1;

private void timer1_Tick(object sender, EventArgs e)
{
    if (ball.Location.Y >= this.Height-50)
        yDir =   -1 ;
    if (ball.Location.X >= this.Width-50) 
        xDir= -1 ;

    ball.MoveBy(xDir*step,yDir*step);
    ball.Host.ElapsedTime++;
    this.Invalidate();
 }

private void DoubleBufferedBall_Paint(object sender, PaintEventArgs e)
{                      
        DrawOn(e.Graphics);
}
0
votes

Another option, that I'll toss out for you, is to do all your drawing to a Bitmap, and then in the OnPaint method, you simply draw that Bitmap to the form.

Its manual, but it gives you full control. I've used it with some success on some pet projects of mine.

You also might want to look into XNA -- it might be overkill for your project here, but you can use XNA in WinForms as a rendering engine.