WinForms native controls flicker and have poor performance

I have two problems with my own user control that uses bitmaps:

  • It flickers if it is redrawn using the .NET Refresh method.
  • It has poor performance.

The control consists of three raster images:

  • Static background image.
  • Rotating rotor.
  • Another image depending on the angle of the rotor.

All used raster images have a resolution of 500x500 pixels. Management works as follows: https://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif (this is a gif animation)

The user control must draw each time it receives a new rotor angle. Therefore, it has the public property "RotorAngle", which is as follows:

public double RotorAngle { get { return mRotorAngle; } set { mRotorAngle = value; Refresh(); } } 

Refresh raises the Paint event. The OnPaint event handler looks like this:

 private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) { // Draw the three bitmaps using a rotation matrix to rotate the rotor bitmap. Draw((float)mRotorAngle); } 

But when I use this code that works well in my other custom user controls - the user control is not drawn at all if the control is double-buffered via SetStyle(ControlStyles.OptimizedDoubleBuffer, true) . If I do not set this flag to true, the control flickers when redrawing.

In the control constructor, I set:

 SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.ContainerControl, false); // User control is not drawn if "OptimizedDoubleBuffer" is true. // SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.ResizeRedraw, true); SetStyle(ControlStyles.SupportsTransparentBackColor, true); 

Firstly, I thought it was flickering because the background is cleared every time the control is drawn. Therefore, I set SetStyle(ControlStyles.AllPaintingInWmPaint, true) . But it did not help.

So why does it flicker? Other controls work very well with this setting. And why is this contradiction not drawn if SetStyle(ControlStyles.OptimizedDoubleBuffer, true) .

I found out that the control does not flicker if I call my Draw method immediately after changing the RotorAngle property:

 public float RotorAngle { get { return mRotorAngle; } set { mRotorAngle = value; Draw(mRotorAngle); } } 

But this leads to very poor performance, especially in full screen mode. Unable to update control every 20 milliseconds. You can try it yourself. Below I will attach the complete solution to Visual Studio 2008.

So why is this such a bad job? There is no problem updating other (native) controls every 20 milliseconds. Is it really just because of bitmaps?

I created a simple visual solution for Visual Studio 2008 to demonstrate two problems: https://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip (289.3 KB)

The bin\Debug directory has an executable file.

Thanks for your help.

+6
source share
5 answers

First, for a LarsTech response, you should use the Graphics context specified in PaintEventArgs . By calling CreateGraphics() inside the Paint handler, you cannot work correctly with OptimizedDoubleBuffer .

Secondly, in the SetStyle block add:

 SetStyle( ControlStyles.Opaque, true ); 

... to prevent the Control base class from populating the background color before calling the Paint handler.

I tested this in your example project, it seemed to eliminate flicker.

+4
source

Do not use CreateGraphics , but use the Graphic object that was passed to you from the paint event:

I changed it like that and added a clear one, since resizing would show a ghost image:

 private void Draw(float rotorAngle, Graphics graphics) { graphics.Clear(SystemColors.Control); graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; // yada-yada-yada // do not dispose since you did not create it: // graphics.Dispose(); } 

called from:

 private void StatorAndRotor2_Paint(object sender, PaintEventArgs e) { Draw((float)mRotorAngle, e.Graphics); } 

In the constructor, enable double buffering, but I think transparency is not needed:

 SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.ContainerControl, false); SetStyle(ControlStyles.OptimizedDoubleBuffer, true); SetStyle(ControlStyles.ResizeRedraw, true); //SetStyle(ControlStyles.SupportsTransparentBackColor, true); 
+4
source

Drawing Paint events on the screen is a tough operation. Drawing in the memory buffer is relatively quick.

Double buffering will improve performance. Instead of drawing graphics, create new graphics and do everything on it.

After drawing the bitmap, copy the entire bitmap into e.Graphics, obtained from PaintEventArgs

Flicker without drawing using GDI + and C #

+2
source

For my answer, I drew inspiration from fooobar.com/questions/569908 / ... and I typed material from LarsTechs above.

To avoid re-creating the full image on all OnPaints, you can use the variable to store the generated image.

 private Bitmap mtexture; 

Use Draw () to create a texture.

 private void Draw(float rotorAngle) { using (var bufferedGraphics = Graphics.FromImage(mtexture)) { Rectangle imagePosition = new Rectangle(0, 0, Width, Height); bufferedGraphics.DrawImage(mStator, imagePosition); bufferedGraphics.DrawImage(RotateImage(mRotor, mRotorAngle), imagePosition); float normedAngle = mRotorAngle % cDegreePerFullRevolution; if (normedAngle < 0) normedAngle += cDegreePerFullRevolution; if (normedAngle >= 330 || normedAngle <= 30) bufferedGraphics.DrawImage(mLED101, imagePosition); if (normedAngle > 30 && normedAngle < 90) bufferedGraphics.DrawImage(mLED001, imagePosition); if (normedAngle >= 90 && normedAngle <= 150) bufferedGraphics.DrawImage(mLED011, imagePosition); if (normedAngle > 150 && normedAngle < 210) bufferedGraphics.DrawImage(mLED010, imagePosition); if (normedAngle >= 210 && normedAngle <= 270) bufferedGraphics.DrawImage(mLED110, imagePosition); if (normedAngle > 270 && normedAngle < 330) bufferedGraphics.DrawImage(mLED100, imagePosition); } } 

Override OnPaint, draw a texture on your control

 protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Rectangle imagePosition = new Rectangle(0, 0, Width, Height); e.Graphics.DrawImage(mtexture, imagePosition); } 

Override OnInvalidated () to draw () texture if necessary

 protected override void OnInvalidated(InvalidateEventArgs e) { base.OnInvalidated(e); if (mtexture != null) { mtexture.Dispose(); mtexture = null; } mtexture = new Bitmap(Width, Height); Draw(mRotorAngle); } 

Instead of calling Draw, invalidate the image. This will cause a redraw using OnInvalidated and OnPaint.

 public float RotorAngle { get { return mRotorAngle; } set { mRotorAngle = value; Invalidate(); } } 

Hope I found everything there :)

0
source

Many thanks for your help.

The flicker is solved. :)

Now I follow the recommendations of lnmx for the hint that CreateGraphics() inside the Paint handler prevents the correct OptimizedDoubleBuffer function from OptimizedDoubleBuffer . This explains the flashing problem, even if the OptimizedDoubleBuffer function is enabled. I did not know this, and I also did not find it on the MSDN Library . In my previous controls, I also used a Graphics object from PaintEventArgs .

Thanks to Sallow for your efforts. I will check your code today and I will give feedback. I hope this improves performance because there is still a performance issue - despite the proper double buffering.

There was another performance issue in my source code.

Modified

 graphics.DrawImage(mStator, imagePosition); graphics.DrawImage(RotateImage(mRotor, rotorAngle), imagePosition); 

to

 graphics.DrawImage(mStator, imagePosition); Bitmap rotatedImage = RotateImage(mRotor, rotorAngle); graphics.DrawImage(rotatedImage, imagePosition); rotatedImage.Dispose(); // Important, otherwise the RAM will be flushed with bitmaps. 
0
source

Source: https://habr.com/ru/post/915689/


All Articles