6

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 (Point 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?

Matt
  • 25,467
  • 18
  • 120
  • 187
LmSNe
  • 143
  • 1
  • 9
  • 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 Jun 18 '10 at 14:08
  • I saw some working code using double buffering, showing an animated ball here: https://stackoverflow.com/a/4923613/1016343 – Matt May 06 '22 at 14:49

5 Answers5

4

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.

Andrey
  • 59,039
  • 12
  • 119
  • 163
  • 2
    yes, but you are deriving from Form. Like when you create basic winforms app it creates Form1 that comes from Form. You can access it from there. If not - you can always create wrapper that exposes this property. – Andrey Jun 18 '10 at 14:41
3
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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

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.

Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • But that would need access to the from object I'm drawing on, right? All the access I have is to the Graphics object that is supplied as a parameter to the function. Is reflection possible in such a situation? – LmSNe Jun 20 '10 at 11:07
  • Yes. It can be done via reflection with just a few lines of code: http://stackoverflow.com/questions/76993/how-to-double-buffer-net-controls-on-a-form – Paul Sasik Jun 20 '10 at 16:55
1

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);
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
0

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.

Nate
  • 30,286
  • 23
  • 113
  • 184
  • Is that what you meant? public void DrawOn(Graphics g){ Bitmap bitmap = new Bitmap(800, 600, g); Graphics g1 = Graphics.FromImage(bitmap); // drawing to g1 ... g.DrawImage(bitmap, new Point(0, 0)); } Because this still flickers. – LmSNe Jun 19 '10 at 10:51
  • Yes, it is, but I'm not sure why it still flickers for you. – Nate Jun 19 '10 at 14:30
  • because it erases before drawing. You need doublebuffering turned on as above. Put this in your constructor: this.DoubleBuffered = true; AND NOWHERE ELSE – John Lord Nov 22 '18 at 20:01