Reputation: 205
I have two problems with an own user control which uses bitmaps:
The control consists of three bitmaps:
All used bitmaps have a resolution of 500x500 pixels. The control works like this: https://www.dropbox.com/s/t92gucestwdkx8z/StatorAndRotor.gif (it's a gif animation)
The user control should draw itself everytime it gets a new rotor angle. Therefore, it has a public property 'RotorAngle' which looks like this:
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 - which works well in other own user controls - the user control is not drawn at all if control is double buffered via SetStyle(ControlStyles.OptimizedDoubleBuffer, true)
. If I don't set this flag to true, the control flickers when being redrawn.
In 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);
First, I thought it flickers because the background is cleared everytime the control is drawn. Therefore, I set SetStyle(ControlStyles.AllPaintingInWmPaint, true)
. But it didn't help.
So, why does it flicker? Other controls work very well with this setup. And why is the controly not drawn if SetStyle(ControlStyles.OptimizedDoubleBuffer, true)
.
I found out that the control does not flicker if I invoke my Draw
method directly after changing the property RotorAngle
:
public float RotorAngle
{
get { return mRotorAngle; }
set
{
mRotorAngle = value;
Draw(mRotorAngle);
}
}
But this results in a very bad performance, especially in full screen mode. It's not possible to update the control every 20 milliseconds. You can try it yourself. I will attach the complete Visual Studio 2008 solution below.
So, why it is such a bad performance? It's no problem to update other (own) controls every 20 milliseconds. Is it really just due to the bitmaps?
I created a simple visual Visual Studio 2008 solution to demonstrate the two problems: https://www.dropbox.com/s/mckmgysjxm0o9e0/WinFormsControlsTest.zip (289,3 KB)
There is an executable in directory bin\Debug
.
Thanks for your help.
Upvotes: 6
Views: 3238
Reputation: 205
Thank you very much for your help.
Flickering is solved. :)
Now, I follow LarsTech's suggestion and use the Graphics
object from PaintEventArgs
.
Thanks lnmx for the hint that CreateGraphics()
inside Paint
handler prevents the correct function of OptimizedDoubleBuffer
. This explains the flickering problem even if OptimizedDoubleBuffer
was enabled. I didn't know that and I also haven't found this at MSDN Library. In my previous controls, I have also used the Graphics
object from PaintEventArgs
.
Thanks Sallow for your efforts. I will test your code today and I will give feedback. I hope this will improve performance because there is still a performance issue - despite correct double buffering.
There was still another performance problem in my original code.
Changed
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.
Upvotes: 0
Reputation: 1475
For my answer I took inspiration from https://stackoverflow.com/a/2608945/455904 and I nabbed stuff from LarsTechs answer above.
To avoid having to regenerate the complete image on all OnPaints you can use a variable to hold the generated image.
private Bitmap mtexture;
Use Draw() to generate the 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);
}
}
Have the override of OnPaint draw the 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() the texture when needed
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 it to repaint using OnInvalidated and OnPaint.
public float RotorAngle
{
get { return mRotorAngle; }
set
{
mRotorAngle = value;
Invalidate();
}
}
I hope I got all of it in there :)
Upvotes: 0
Reputation: 11154
First, per LarsTech's answer, you should use the Graphics
context provided in the PaintEventArgs
. By calling CreateGraphics()
inside the Paint
handler, you prevent OptimizedDoubleBuffer
from working correctly.
Second, in your SetStyle block, add:
SetStyle( ControlStyles.Opaque, true );
... to prevent the base class Control from filling in the background color before calling your Paint handler.
I tested this in your example project, it seemed to eliminate the flickering.
Upvotes: 4
Reputation: 81610
Do not use CreateGraphics
, but use the Graphic object that was passed to you from the paint event:
I changed it like so and added a clear since resizing would show the 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, turn on double-buffering, but I think the transparency isn't needed:
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.ContainerControl, false);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.ResizeRedraw, true);
//SetStyle(ControlStyles.SupportsTransparentBackColor, true);
Upvotes: 4
Reputation: 30688
Drawing at screen on Paint event is a heavy operation. Doing paint on memory buffer is comparatively very fast.
Double buffering will improve the performance. Instead of drawing on paint graphics, create a new graphics, and do all drawing on it.
Once bitmap drawing is done, copy the entire bitmap to e.Graphics received from PaintEventArgs
Flicker free drawing using GDI+ and C#
Upvotes: 2