Own WinForms Control Flickers and Has Bad Performance - c#

I have two problems with an own user control which uses bitmaps:
It flickers if it's redrawn via .NET's 'Refresh' method.
It has a bad performance.
The control consists of three bitmaps:
A static background image.
A rotating rotor.
Another image depending on rotor angle.
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.

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.

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);

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#

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 :)

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.

Related

Bitmap repaint in picturebox lagging

I have application in which you can draw some shapes, catch them by vertices and move the vertice. I store vertices of a shapes in the List and repaint whole list of the objects (when the vertice is catch and mouse moves)in the bitmap which is assigned to PictureBox.Image. When I add more than 5 shapes, the moving vertice is lagging. Here is a piece of code:
private void DrawFullList()
{
if (pictureBox2.Image != null)
{
pictureBox2.Image.Dispose();
g.Dispose();
}
graphic = new Bitmap(pictureBox2.Width, pictureBox2.Height);
g = Graphics.FromImage(graphic);
pictureBox2.Image = graphic;
for (int i = 0; i < PointsList.Count; i++)
Draw(BrushList[i], PointsList[i]);
}
private void Draw(Brush brush, Point[] points)
{
Pen PathPen = new Pen(brush);
PathPen.Width = 3;
if (points.Length == 2)
g.DrawLine(PathPen, points[0], points[1]);
else
g.FillPolygon(brush,points);
pictureBox2.Image = graphic;
}
If there anyway to imporve it? I was trying to graphic.Clear(Color.Transparent) but there was no way to change the size of bitmap ( the function is used when we resizing the window).
Any tips?
I found simple mistake what actually makes the lags. pictureBox2.Image = graphic; was executed two times in a row, when PointsList.Count != 0 what was creating laggs.
Your code looks overcomplicated and ineffective. Also your code rely too hard on garbage collector (it is a good practice to dispose Graphics, Brush and Pen classes right after use).
I think in your case best idea is to avoid creating and disposing bitmaps completely. You can replace your PictureBox with, for example, Panel class, subscribe to its Paint event and paint your shapes inside this method. When position of your vertices changes simply call Invalidate method to repaint your shapes inside panel.

C# Picturebox Image is null when I draw a shape in Paint event [duplicate]

in a UserControl I have a PictureBox and some other controls. For the user control which contains this picturebox named as Graph I have a method to draw a curve on this picture box:
//Method to draw X and Y axis on the graph
private bool DrawAxis(PaintEventArgs e)
{
var g = e.Graphics;
g.DrawLine(_penAxisMain, (float)(Graph.Bounds.Width / 2), 0, (float)(Graph.Bounds.Width / 2), (float)Bounds.Height);
g.DrawLine(_penAxisMain, 0, (float)(Graph.Bounds.Height / 2), Graph.Bounds.Width, (float)(Graph.Bounds.Height / 2));
return true;
}
//Painting the Graph
private void Graph_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
DrawAxis(e);
}
//Public method to draw curve on picturebox
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
var g = Graphics.FromImage(bmp);
g.DrawCurve(_penAxisMain, points);
Graph.Image = bmp;
g.Dispose();
}
When application starts, the axis are drawn. but when I call the DrawData method I get the exception that says bmp is null. What can be the problem?
I also want to be able to call DrawData multiple times to show multiple curves when user clicks some buttons. What is the best way to achive this?
Thanks
You never assigned Image, right? If you want to draw on a PictureBox’ image you need to create this image first by assigning it a bitmap with the dimensions of the PictureBox:
Graph.Image = new System.Drawing.Bitmap(Graph.Width, Graph.Height);
You only need to do this once, the image can then be reused if you want to redraw whatever’s on there.
You can then subsequently use this image for drawing. For more information, refer to the documentation.
By the way, this is totally independent from drawing on the PictureBox in the Paint event handler. The latter draws on the control directly, while the Image serves as a backbuffer which is painted on the control automatically (but you do need to invoke Invalidate to trigger a redraw, after drawing on the backbuffer).
Furthermore, it makes no sense to re-assign the bitmap to the PictureBox.Image property after drawing. The operation is meaningless.
Something else, since the Graphics object is disposable, you should put it in a using block rather than disposing it manually. This guarantees correct disposing in the face of exceptions:
public void DrawData(PointF[] points)
{
var bmp = Graph.Image;
using(var g = Graphics.FromImage(bmp)) {
// Probably necessary for you:
g.Clear();
g.DrawCurve(_penAxisMain, points);
}
Graph.Invalidate(); // Trigger redraw of the control.
}
You should consider this as a fixed pattern.

Existing Graphics into Bitmap

I'm writing a plugin for a trading software (C#, winforms, .NET 3.5) and I'd like to draw a crosshair cursor over a panel (let's say ChartPanel) which contains data that might be expensive to paint. What I've done so far is:
I added a CursorControl to the panel
this CursorControl is positioned over the main drawing panel so that it covers it's entire area
it's Enabled = false so that all input events are passed to the parent
ChartPanel
it's Paint method is implemented so that it draws lines from top to bottom and from left to right at current mouse position
When MouseMove event is fired, I have two possibilities:
A) Call ChartPanel.Invalidate(), but as I said, the underlying data may be expensive to paint and this would cause everything to redraw everytime I move a mouse, which is wrong (but it is the only way I can make this work now)
B) Call CursorControl.Invalidate() and before the cursor is drawn I would take a snapshot of currently drawn data and keep it as a background for the cursor that would be just restored everytime the cursor needs to be repainted ... the problem with this is ... I don't know how to do that.
2.B. Would mean to:
Turn existing Graphics object into Bitmap (it (the Graphics) is given to me through Paint method and I have to paint at it, so I just can't create a new Graphics object ... maybe I get it wrong, but that's the way I understand it)
before the crosshair is painted, restore the Graphics contents from the Bitmap and repaint the crosshair
I can't control the process of painting the expensive data. I can just access my CursorControl and it's methods that are called through the API.
So is there any way to store existing Graphics contents into Bitmap and restore it later? Or is there any better way to solve this problem?
RESOLVED: So after many hours of trial and error I came up with a working solution. There are many issues with the software I use that can't be discussed generally, but the main principles are clear:
existing Graphics with already painted stuff can't be converted to Bitmap directly, instead I had to use panel.DrawToBitmap method first mentioned in #Gusman's answer. I knew about it, I wanted to avoid it, but in the end I had to accept, because it seems to be the only way
also I wanted to avoid double drawing of every frame, so the first crosshair paint is always drawn directly to the ChartPanel. After the mouse moves without changing the chart image I take a snapshow through DrawToBitmap and proceed as described in chosen answer.
The control has to be Opaque (not enabled Transparent background) so that refreshing it doesn't call Paint on it's parent controls (which would cause the whole chart to repaint)
I still experience occasional flicker every few seconds or so, but I guess I can figure that out somehow. Although I picked Gusman's answer, I would like to thank everyone involved, as I used many other tricks mentioned in other answers, like the Panel.BackgroundImage, use of Plot() method instead of Paint() to lock the image, etc.
This can be done in several ways, always storing the graphics as a Bitmap. The most direct and efficient way is to let the Panel do all the work for you.
Here is the idea: Most winforms Controls have a two-layered display.
In the case of a Panel the two layers are its BackgroundImage and its Control surface.
The same is true for many other controls, like Label, CheckBox, RadioButton or Button.
(One interesting exception is PictureBox, which in addition has an (Foreground) Image. )
So we can move the expensive stuff into the BackgroundImage and draw the crosshair on the surcafe.
In our case, the Panel, all nice extras are in place and you could pick all values for the BackgroundImageLayout property, including Tile, Stretch, Center or Zoom. We choose None.
Now we add one flag to your project:
bool panelLocked = false;
and a function to set it as needed:
void lockPanel( bool lockIt)
{
if (lockIt)
{
Bitmap bmp = new Bitmap(panel1.ClientSize.Width, panel1.ClientSize.Width);
panel1.DrawToBitmap(bmp, panel1.ClientRectangle);
panel1.BackgroundImage = bmp;
}
else
{
if (panel1.BackgroundImage != null)
panel1.BackgroundImage.Dispose();
panel1.BackgroundImage = null;
}
panelLocked = lockIt;
}
Here you can see the magic at work: Before we actually lock the Panel from doing the expensive stuff, we tell it to create a snapshot of its graphics and put it into the BackgroundImage..
Now we need to use the flag to control the Paint event:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Size size = panel1.ClientSize;
if (panelLocked)
{
// draw a full size cross-hair cursor over the whole Panel
// change this to suit your own needs!
e.Graphics.DrawLine(Pens.Red, 0, mouseCursor.Y, size.Width - 1, mouseCursor.Y);
e.Graphics.DrawLine(Pens.Red, mouseCursor.X, 0, mouseCursor.X, size.Height);
}
// expensive drawing, you insert your own stuff here..
else
{
List<Pen> pens = new List<Pen>();
for (int i = 0; i < 111; i++)
pens.Add(new Pen(Color.FromArgb(R.Next(111),
R.Next(111), R.Next(111), R.Next(111)), R.Next(5) / 2f));
for (int i = 0; i < 11111; i++)
e.Graphics.DrawEllipse(pens[R.Next(pens.Count)], R.Next(211),
R.Next(211), 1 + R.Next(11), 1 + R.Next(11));
}
}
Finally we script the MouseMove of the Panel:
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
mouseCursor = e.Location;
if (panelLocked) panel1.Invalidate();
}
using a second class level variable:
Point mouseCursor = Point.Empty;
You call lockPanel(true) or lockPanel(false) as needed..
If you implement this directly you will notice some flicker. This goes away if you use a double-buffered Panel:
class DrawPanel : Panel
{
public DrawPanel() { this.DoubleBuffered = true; }
}
This moves the crosshair over the Panels in a perfectly smooth way. You may want to turn on & off the Mouse cursor upon MouseLeave and MouseEnter..
Why don't you clone all the graphics in the ChartPanel over your CursorControl?
All the code here must be placed inside your CursorControl.
First, create a property which will hold a reference to the chart and hook to it's paint event, something like this:
ChartPanel panel;
public ChartPanel Panel
{
get{ return panel; }
set{
if(panel != null)
panel.Paint -= CloneAspect;
panel = value;
panel.Paint += CloneAspect;
}
}
Now define the CloneAspect function which will render the control's appearance to a bitmap whenever a Paint opperation has been done in the Chart panel:
Bitmap aspect;
void CloneAspect(object sender, PaintEventArgs e)
{
if(aspect == null || aspect.Width != panel.Width || aspect.Height != panel.Height)
{
if(aspect != null)
aspect.Dispose();
aspect = new Bitmap(panel.Width, panel.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
}
panel.DrawToBitmap(aspect, new Rectangle(0,0, panel.Width, panel.Height);
}
Then in the OnPaint overriden method do this:
public override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(aspect);
//Now draw the cursor
(...)
}
And finally wherever you create the chart and the customcursor you do:
CursorControl.Panel = ChartPanel;
And voila, you can redraw as many times you need without recalculating the chart's content.
Cheers.

Imitating MSpaints drawing methods

For graphics exercises and some self-improvement sorta stuff, i've decided to basically just mess around and try and recreate some of the functionality of paint within a winform. I've got a lot of the standard ones to work, for example paint can, drawing dots around the cursor, free hand draw and what not, however i'm a little puzzled as to how paint does the mid-drawing animations. For example;
To draw a simple line i can simply get mouse coordinates on MouseUp and MouseDown events, and use the graphics class to draw a line between the two.
However, on MSpaint whilst drawing a line, you get almost a "preview" of the line after you click the first point, and whilst dragging it to the second point the line follows your cursor, but i'm a little stuck as to how this would be done? Does it involve constant redrawing of the line and the graphics device? It'd be great if someone could give me some hints / inner knowledge, i've had a search around the internet but can't REALLY find anything of use..
And very modern via ControlPaint.DrawReversibleLine method :)
Point? startPoint;
Point? endPoint;
private void Form_MouseDown(object sender, MouseEventArgs e)
{
startPoint = PointToScreen(e.Location);
}
private void Form_MouseMove(object sender, MouseEventArgs e)
{
if (!startPoint.HasValue)
return;
if (endPoint.HasValue)
ControlPaint.DrawReversibleLine(startPoint.Value, endPoint.Value, Color.White);
endPoint = PointToScreen(e.Location);
ControlPaint.DrawReversibleLine(startPoint.Value, endPoint.Value, Color.White);
}
private void Form_MouseUp(object sender, MouseEventArgs e)
{
startPoint = null;
endPoint = null;
}
Bitmap/raster software makes use of two memory buffers: one is the current "persisted" canvas that contains the pixels that the user has modified explicitly, the second is the framebuffer on the graphics card which is used to display the canvas on-screen.
Making the bitmap document appear on-screen is done by simply copying the raw bytes of the in-memory bitmap document to the framebuffer (if the framebuffer has a different byte-format or color-depth than the in-memory bitmap then you need to perform a conversion. GDI can do this for you if necessary, but let's just assume everything is 32-bit ARGB).
In WinForms, the framebuffer is exposed by the Graphics argument passed into your Control.OnPaint override.
You can implement these "preview" effects using one of two approaches:
Modern
The first approach is used today, and has been for the past 17 years or so (since Windows 95). The in-memory bitmap is copied to the framebuffer whenever the screen needs to be updated, such as a single mouse movement (of even 1px). The preview effect (such as the line the user would be painting once they release the mouse button) is then drawn on-top. The process is repeated as soon as the user's mouse moves again, so to update the preview.
You'd have something like this:
public class PaintingCanvas : Control {
private Bitmap _canvas = new Bitmap();
private Boolean _inOp; // are we in a mouse operation?
private Point _opStart; // where the current mouse operation started
private Point _opEnd; // where it ends
public override void OnPaint(PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawImage( _canvas ); // draw the current state
if( _inOp ) {
// assuming the only operation is to draw a line
g.DrawLine( _opStart, _opEnd );
}
}
protected override OnMouseDown(Point p) {
_inOp = true;
_opStart = _opEnd = p;
}
protected override OnMouseMove(Point p) {
_opEnd = p;
this.Invalidate(); // trigger repainting
}
protected override OnMouseUp(Point p) {
using( Graphics g = Graphics.FromImage( _bitmap ) ) {
g.DrawLine( _opStart, _opEnd ); // a permanent line
}
_inOp = false;
}
}
1980s Flashblack
In ye olden days (think: 1980s), copying the bitmap from memory to the framebuffer was slow, so a surprisingly good hack was using XOR painting. The program assumes ownership of the framebuffer (so no overlapping windows would cause it need to be copied from memory). The preview line is drawn by performing an XOR of all of the pixels that the line would cover. This is fast because XORing the pixels means that their original colour can be restored without needing to recopy the pixels from memory. This trick was used in computers for many kinds of selection, highlight, or preview effect until recently.
highlightOrPreviewColor = originalPixelColor XOR (2^bpp - 1)
originalPixelColor = highlightOrPreviewColor XOR (2^bpp - 1)

Windows Form, display indicator over a dynamically generated bitmap

I have a usercontrol that displays a gradient of colors which, once created will be constant.
The usercontrol doesn't contain any controls, not sure if i need to add a picturebox or dynamically add one.
Over that image, I'd like to display a line that will display what the current result is. I have no problem creating the gradient image on the map, however I'd like to somehow cache it so everytime I update the indicator (call CurrentValue from parent form), it will put the indicator line above the gradient image. This is updating about 30 times a second, thus, as of how the code below is working, it's repainting the gradient everytime, which is flickering.
Here's a code sample:
namespace Maps.UserControls
{
public partial class UserControlLegend : UserControl
{
private double m_CurrentValue;
public double CurrentValue
{
get
{
return m_CurrentValue;
}
set
{
m_CurrentValue = value;
RefreshValue();
}
}
public UserControlLegend()
{
InitializeComponent();
}
private void UserControlLegend_Paint(object sender, PaintEventArgs e)
{
if (b == null)
{
g = e.Graphics;
b = new System.Drawing.Bitmap(menuWidth, menuHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// Code here that draws Menu
// Cache bitmap here?
g.Dispose();
}
}
private void RefreshValue()
{
this.Refresh();
g = this.CreateGraphics();
g.DrawImage(b, 0, 0);
//Code to Calcuate current Indicator Location
int x3 = 0;
// Draws current indicator correctly
g.DrawLine(new Pen(new SolidBrush(Color.Black)), this.Width / 2 - 15, x3, this.Width / 2 - 5, x3);
g.Dispose();
}
}
}
Explained above in comments, used a bitmap, and just set the x,y of the control.
First, I'd suggest you set your Control's property DoubleBuffered to True, so that flickering goes away. However, if you don't draw on the Control itself, that will be not useful at all. Drawing on a PictureBox is better, however, becuase it is automatically DoubleBuffered.
Second, you are painting into a new Bitmap every time, which is very bad in terms of memory, since the Bitmap is a few megabytes in size. I'd suggest you have a single Bitmap initialized in the constructor, and a single Graphics, created from that Bitmap in the constructor, too. Every time the paint accurs, just clear the old Graphics g and then draw onto it again and again. g Graphics and b Bitmap should be Disposed one time only, when the entire Control is Disposed.
This may inhance your code.

Categories

Resources