C# Threading Bitmap objects/PictureBox - c#

I have some code to display video-like graphics of points moving around.
I am writing the points to a bitmap, and placing it on a picturebox.
The graphics computation has to be done on its own thread. The graphics work fine as long as you don't move the window around "too" much.
I'm using winforms. When I run the code, and move the window around wildly, I SOMETIMES get the following errors:
# this.Invoke(d, new object[ ] { bmp
}); "Cannot access a disposed
object. Object name: 'Form1'."
# gfx.DrawImage(bmpDestination, new
Point()); "Object is currently in
use elsewhere."
Here is the code:
private void button2_Click(object sender, EventArgs e)
{
Thread demoThread = new Thread(new ThreadStart(ThreadProcSafe));
demoThread.Start();
}
private void ThreadProcSafe()
{
creategraphics();
}
private void creategraphics()
{
Bitmap bmpDestination = new Bitmap(988, 588);
Bitmap bmp = new Bitmap(988, 588);
for (int i = 0; i < numtimesteps; i++)
{
bmp = GraphingUtility.create(apple, i, 988, 588, -30, 30, -30, 30);
using (Graphics gfx = Graphics.FromImage(bmp))
{
gfx.DrawImage(bmpDestination, new Point());
}
bmpDestination = bmp;
updateimage(bmp);
}
}
delegate void graphicscallback(Bitmap bmp);
private void updateimage(Bitmap bmp)
{
if (pictureBox1.InvokeRequired)
{
graphicscallback d = new graphicscallback(updateimage);
this.Invoke(d, new object[] { bmp });
}
else
{
pictureBox1.Image = bmp;
pictureBox1.Refresh();
}
}

There are problems using GDI objects across threads.
Look at Hans' double-clone suggestions in this thread.

Cannot access a disposed object. Object name: 'Form1
You get this because you don't do anything to stop the thread when the user closes the form. You'll have to keep the form alive until you know that the thread is dead. Pretty hard to do reliable, check this answer for a solution.
As an aside, calling InvokeRequired is an anti-pattern. You know that you are making the call from a worker thread. If InvokeRequired would return false then there's something really wrong. Don't bother, call Invoke() directly.
Object is currently in use elsewhere
It is an exception raised by GDI+ (Graphics) when it sees that two threads are trying to access a bitmap at the same time. It isn't obvious in your snippet but I can't see what GraphingUtility.create() does. Make sure it creates a new bitmap, not return an existing one. Because that will bomb when your thread writes to it again and the picture box repaints itself at the same time. The bmp constructor you use above it doesn't do anything.
Your PictureBox.Image assignment is forgetting to dispose the old one.

Related

C# Draw on a PictureBox

Suppose i have an PictureBox with image assigned to it, and i just want to draw a smaller bitmap above it.
This is my code:
Bitmap a = Getimage();//just a small function generates a new image.
Bitmap f = (Bitmap) pictureBox1.Image;//getting the picturebox image.
Graphics g = Graphics.FromImage(f);
g.DrawImage(a,150,200);//draws a on f at (150,200).
PictureBox1.Image=f;
However, in my program it runs on a loop in a seperated thread, so im getting an Error:
Object is in use elsewhere
Is there any way to draw directly to the Picturebox itself? instead of getting it's bitmap and draw, and assign again? Or at least how may i solve the above exception?
Thanks.
this will help you
public Form1()
{
InitializeComponent();
new Thread(MyDraw).Start();
}
private void MyDraw()
{
while(true)
{
Invoke(new Action(DrawItem));
Thread.Sleep(1000);
}
}
private void DrawItem()
{
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawString("Hello", new Font("Arial", 20), Brushes.Yellow, PointF.Empty);
}
}

Creating a custom graph in Winforms

I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;

C#, double buffer in WinForms?

private void button3_Click(object sender, EventArgs e)
{
this.DoubleBuffered = true;
for (int i = 0; i < 350; i++)
{
using (Graphics g = this.CreateGraphics() )
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
}
Yet thought I have the DoubleBuffered set to true, the image still flickers. Any ideas what am I doing wrong?
Thanks!
As Neil noted, you don't need to (and shouldn't) create a new Graphics object in each iteration of the loop. These are relatively expensive resources and should not be created willy nilly.
Also, you shouldn't be painting like that inside of a button Click handler by calling CreateGraphics. It can lead to problems, most notably your drawing being "undone" when the paint handler is invoked (i.e., every time the window is receives a WM_PAINT message and is refreshed). You should do all of your painting by overriding OnPaint and simply call Invalidate() when you need to update your form.
As for the flickering, setting DoubleBuffered to true will usually take care of it, but rolling your own double buffering is trivial. Give it a try. Also realize that drawing in a loop like that probably isn't what you want to do. Use a timer if you want to update once per some interval. Your code is being executed as fast as the loop can execute, which is not usually desirable.
private void someTimer_Tick( ... )
{
Invalidate();
}
protected override void OnPaint( PaintEventArgs e )
{
using( var tempBmp = new Bitmap( ... ) )
using( var g = Graphics.FromImage( tempBmp ) )
{
// draw to tempBmp
e.Graphics.DrawImage( tempBmp, new Point( 0, 0 ) );
}
}
The problem is you are creating a new graphics object on each iteration of the loop
Move the for statement within the using statement, and you should see a dramatic performance increase:
using (Graphics g = this.CreateGraphics() )
{
for (int i = 0; i < 350; i++)
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
That, and it may also be a good idea to move the Resource file you are loading into a local variable.
Double-buffering is only enabled for the Paint event. You are directly drawing to the screen with CreateGraphics(), the g.Clear() call is very noticeable since it instantly erases the drawn image. Not drawing in the Paint event or OnPaint method is almost always a mistake.

How to erase content on a Bitmap

I have C# winForm application which holds a pictureBox control. This control has a Paint event. Every time paint event is fired , a Bitmap is dynamically created and I perform some drawing on it. And when user clicks "save" button , edited image is saved as jpg file.
It's OK until now. When I load a new image in pictureBox control remains of previous edits are still alive.
How can I erase the bitmap and start fresh each time I load a new image:
private void pb_Resim_Paint(object sender, PaintEventArgs e)
{
List<eVucutParcalari> list = new List<eVucutParcalari>(pointList.Keys);
// Loop through list
foreach (eVucutParcalari k in list)
{
Dictionary<Point, Color> dicItem = pointList[k];
foreach (KeyValuePair<Point, Color> pair in dicItem)
{
Point p = pair.Key;
Color c = pair.Value;
SolidBrush brush = new SolidBrush(c);
if (pb_Resim.Image == null)
return;
Bitmap bmp = new Bitmap(pb_Resim.Image);
Graphics gr = Graphics.FromImage(bmp);
gr.FillRectangle(brush, p.X, p.Y, 5, 5);
pb_Resim.Image = bmp;
}
}
}
Why not create a brand new bitmap when you load a new file, and replace the bitmap currently assigned to pb_Resim.Image on load? This would allow the old bitmap to be collected by the GC without requiring you to expend any effort "clearing" the previous bitmap, and ensure that you have a brand new, fresh bitmap without any residual junk of any kind for the newly loaded file.
Christian Graus wrote several articles on GDI+, which you can find on CodeProject. (Scroll down to the GDI+ articles.) Since they directly relate to the type of activity you are doing, I suggest looking through them.
I think you meant clear the picture box of the previous content which happens to be the bitmap object of an image file.
You have already done
new brush, new bitmap, getting new graphics canvas from the new bitmap.
Any chance of doing a new PictureBox?
Because that's what I did when my app had to quick-and-dirty repeatedly display a set of images. I'm sure there's a better way to clear the picturebox, like using refresh().
foreach ( ... )
{
pb_Resim = new PictureBox();
configPictureBoxDimensions();
...
}
Why don't you try refresh() first? It should work.
foreach ( ... )
{
pb_Resim = bmp;
pb_Resim.refresh();
...
}

Am I responsible for Disposing a BackgroundImage?

I have a windows form where I set the BackgroundImage property to a custom bitmap image.
private Image MakeCustomBackground()
{
Bitmap result = new Bitmap(100, 100);
using(Graphics canvas = Graphics.FromImage(result))
{
// draw the custom image
}
return result;
}
private void UpdateFromBackground()
{
this.BackgroundImage = MakeCustomBackground();
}
My question is, Image is disposable and I am creating it, does that mean that I must dispose of it? Or when I pass the image to the form, via BackgroundImage, does it take ownership and dispose of it when it no longer needs it?
Assuming that UpdateFromBackground() is called more than once, you probably should Dispose the old Image when (before) setting a new one. If you don't then the GC will do it eventually but that is less efficient. The Form will only Dispose the last BgImage you assigned.
private void UpdateFromBackground()
{
if (this.BackgroundImage != null)
{
this.BackgroundImage.Dispose();
}
this.BackgroundImage = MakeCustomBackground();
}

Categories