سؤال

I have a WinForm application 'Bouncing Balls' , and I need to paint the balls on a bitmap and present the bitmap on this form.

I have a plusButton that adds new ball, and i'm saving each new ball in a list.

Now, the Form_Paint method is telling to each ball to draw himself, it works fine until there are a lot of balls and the all application become very slow..

Here is my Code:

The paint method of the form code:

 private void Form1_Paint(object sender, PaintEventArgs e)
 {
     ballsArray.drawImage(bmp,e, ClientRectangle);
 }

NOTE: ballsArray is from type AllBalls, this is a class that wraps the ball methods, inside his c'tor i'm creating a list that keeps each ball. the bmp, is created when the form is loading - on Form_Load() method.

The drawImage of ballsArray code:

 public void drawImage(Bitmap bmp,PaintEventArgs e, Rectangle r)
 {
     foreach (Ball b in allBalls)
     {
         b.drawImage(bmp,e, r);
     }
 }

The drawImage of Ball code:

  public void drawImage(Bitmap bmp, PaintEventArgs e, Rectangle r)
  {
      using (Graphics g = Graphics.FromImage(bmp))
      {
          e.Graphics.FillEllipse(brush, ballLocation);
          g.DrawImage(bmp, 0, 0);
      }
  }

NOTE: ballLocation is a rectangle that represent the location of the ball in each step of movement..

So what I'm doing wrong? What causing the application to be slowly?

I have a constraint to draw everything on the bitmap and present it on the form. I'm also passing the bitmap that I create when the form is loading, because I need to draw each on it.

هل كانت مفيدة؟

المحلول

Some basic techniques to make this fast:

  • Don't double-buffer yourself and especially don't double-buffer twice. The double-buffering you get by setting the form's DoubleBuffer property to true is superior to most any double-buffering you'd do yourself. The buffer is highly optimized to work efficiently with your video adapter's settings. So completely drop your bmp variable and draw to the e.Graphics you got from the Paint event handler argument.

  • You are not using the passed r argument. Possibly intended to support clipping invisible balls. The one you want to pass is e.ClipRectangle, you can skip painting balls that are completely outside of this rectangle. While that's an optimization, it isn't one that's commonly useful when you use the Aero theme and you do get inconsistent redraw rates so you might want to skip that one.

  • It isn't very clear why you use both Graphics.FillEllipse and Graphics.DrawImage when you draw the ball. The image ought to overlap the circle so just remove FillEllipse.

  • Pay a lot of attention to the Bitmap object that stores the ball graphic. First thing you want to make sure is that it is drawn with the exact size of the image so it doesn't have to be rescaled. Rescaling is very expensive. While you don't have any rescaling in your DrawImage() call, you will still get it if the resolution of the bitmap is not the same as the resolution of your video adapter. The next step will solve that

  • The pixel format of the ball bitmap is very important. You want one that permits copying the bitmap straight to video memory without any format conversion. On any modern machine, that format is PixelFormat.Format32bppPArgb. The difference is enormous, it draws ten times faster than any of the other ones. You won't get this format from an image resource you added, you'll have to create that bitmap when your program starts up. Check this answer for the required code.

You ought to be able to render at least 15 times faster when you follow these guidelines. If that's still enough then you do need to turn to DirectX, it has the unbeatable advantage of being able to store the ball graphic in video memory so you don't get the expensive blt from main memory to video memory.

نصائح أخرى

DrawImage on Paint (or for that matter on MouseMove) is very bad design.

Graphics.DrawImage is expensive operation, and to the screen it is extra expensive. To improve your user experience (slowness), You should paint on MouseDown/MouseUp events.

In addition, First draw to MemoryBuffer in your drawImage method and after preparing the final image, draw it once on the UI. This technique is known as double buffering.

Don't Flicker! Double Buffer! - CodeProject

In addition you can also look at BitBlit Native API for fast color/image transfer to screen.

A minimalistic c# example is here

Enable double-buffering on your form (DoubleBuffered = true).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top