graphics help

P

Peter Duniho

Ringo said:
[...]
Then nothing ever draws. What am I missing.

Is this the correct way to do it, or was I closer the first time with
Draw_sonar2?

Well, what is "m_objDrawingSurface"?

Based on the "before" code, it seems to be some sort of Image object
(Bitmap, Metafile, whatever). It seems to me that the original code was
not actually all that bad. The one obvious thing I see is that you
should not need to erase the background in the Paint handler. The
control will get a separate event to do that. (But you do need to erase
the background in your image object, of course).

But other than that, the original code is actually more efficient,
because you draw once into some cached image object, and use that to
update the control whenever it actually needs updating. This way you
only update the image object when the data changes, and otherwise if the
control needs redrawing for some reason other than that (overlapping
windows being moved, for example) all you have to do is copy the image
that's already been drawn.

However, if you really want to fix the "after" code, you're going to
need to draw to the Graphics object in the event rather than the
"m_objDrawingSurface" object. If you do it that way, you should just
get rid of the "m_objDrawingSurface" altogether. There's no need to
have it if you are going to redraw everything every time the control
needs updating. (Getting rid of that object will also eliminate the bug
you have where you get a Graphics object from the "m_objDrawingSurface"
twice :) ).

Pete
 
R

Ringo

I need some help. I have an app that talks to a sonar board via the
serial port. In my sp_DataReceived I gather all the data and put it
into arrays. Then I want to draw the data in a pictureBox. I wrote a
function called Draw_sonar2 that does all the drawing. It usually
works but sometimes crashes saying the graphics object is already in
use. After digging I saw that I should be doing the Drawing in the
PB.paint, not a seperate function. My problem is how to move the code
from Draw_sonar2 to pb.paint.

Here are my current functions


private void DrawSonar2()
{
Graphics objGraphics;
objGraphics = Graphics.FromImage(m_objDrawingSurface);
objGraphics.Clear(SystemColors.Control);
int startX, startY, endX, endY;
int angle;
startX = m_objDrawingSurface.Width / 2;
startY = m_objDrawingSurface.Height - robotDiameter/2;

for (int x = 0; x <= 9; x++)
{
int Angle = SA.sensorAngle[x];
int BeamWidth = SA.sensorBeamWidth[x];
int Range = SA.Range[x]*int.Parse(textBox_scale.Text);
if (Range > int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text))
Range = int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text);
for (angle = (Angle - (BeamWidth / 2)); angle < (Angle
+ (BeamWidth / 2)); angle += 2)
{
endX = startX + (int)(Range * Math.Cos(angle *
6.28 / 360));
endY = startY + (int)(Range * Math.Sin(angle *
6.28 / 360));
objGraphics.DrawLine(Pens.Green, startX, startY,
endX, endY);

}
}

objGraphics.Dispose();
PB.Invalidate();
}


private void PB_Paint(object sender, PaintEventArgs e)
{

/////////////////////////////////
Graphics objGraphics;
objGraphics = e.Graphics;
objGraphics.Clear(SystemColors.Control);

objGraphics.DrawImage(m_objDrawingSurface, 0, 0,
m_objDrawingSurface.Width, m_objDrawingSurface.Height);
// objGraphics.Dispose();
}


if I change the line in sp_DataReceived from
DrawSonar2();
to
PB.Invalidate();

And then move the code into PB_paint like this

private void PB_Paint(object sender, PaintEventArgs e)
{
Graphics objGraphics;
objGraphics = Graphics.FromImage(m_objDrawingSurface);
objGraphics.Clear(SystemColors.Control);
// Rectangle rectBounds;
int startX, startY, endX, endY;
int angle;
startX = m_objDrawingSurface.Width / 2;
startY = m_objDrawingSurface.Height - robotDiameter / 2;

objGraphics = Graphics.FromImage(m_objDrawingSurface);
for (int x = 0; x <= 9; x++)
{
int Angle = SA.sensorAngle[x];
int BeamWidth = SA.sensorBeamWidth[x];
int Range = SA.Range[x] *
int.Parse(textBox_scale.Text);
if (Range > int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text))
Range = int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text);
for (angle = (Angle - (BeamWidth / 2)); angle < (Angle
+ (BeamWidth / 2)); angle += 2)
{
endX = startX + (int)(Range * Math.Cos(angle *
6.28 / 360));
endY = startY + (int)(Range * Math.Sin(angle *
6.28 / 360));
objGraphics.DrawLine(Pens.Green, startX, startY,
endX, endY);

}
}

objGraphics.Dispose();
}



Then nothing ever draws. What am I missing.

Is this the correct way to do it, or was I closer the first time with
Draw_sonar2?

Thanks
Ringo
 
R

Ringo

Ringowrote:
[...]
Then nothing ever draws. What am I missing.
Is this the correct way to do it, or was I closer the first time with
Draw_sonar2?

Well, what is "m_objDrawingSurface"?

Based on the "before" code, it seems to be some sort of Image object
(Bitmap, Metafile, whatever). It seems to me that the original code was
not actually all that bad. The one obvious thing I see is that you
should not need to erase the background in the Paint handler. The
control will get a separate event to do that. (But you do need to erase
the background in your image object, of course).

But other than that, the original code is actually more efficient,
because you draw once into some cached image object, and use that to
update the control whenever it actually needs updating. This way you
only update the image object when the data changes, and otherwise if the
control needs redrawing for some reason other than that (overlapping
windows being moved, for example) all you have to do is copy the image
that's already been drawn.

However, if you really want to fix the "after" code, you're going to
need to draw to the Graphics object in the event rather than the
"m_objDrawingSurface" object. If you do it that way, you should just
get rid of the "m_objDrawingSurface" altogether. There's no need to
have it if you are going to redraw everything every time the control
needs updating. (Getting rid of that object will also eliminate the bug
you have where you get a Graphics object from the "m_objDrawingSurface"
twice :) ).

Pete

Thanks Pete,
Here is the definition
private System.Drawing.Bitmap m_objDrawingSurface;

I commented out the line that erases the background, still get the
error.
Here is the exact error
"System.InvalidOperationException was unhandled
Message="Object is currently in use elsewhere."
Source="System.Drawing"
StackTrace:
at System.Drawing.Image.get_Width()
at EzExpander.Form1.DrawSonar2() in C:\Documents and Settings
\Ringo\My Documents\thumb\fw\sonarWizard\work\v1.0.0\SonarWizardApp
\SonarWizard\Form1.cs:line 94
at EzExpander.Form1.sp_DataReceived(Object sender,
SerialDataReceivedEventArgs e) in C:\Documents and Settings\Ringo\My
Documents\thumb\fw\sonarWizard\work\v1.0.0\SonarWizardApp\SonarWizard
\Form1.cs:line 283
at System.IO.Ports.SerialPort.CatchReceivedEvents(Object src,
SerialDataReceivedEventArgs e)
at
System.IO.Ports.SerialStream.EventLoopRunner.CallReceiveEvents(Object
state)
at
System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object
state)
at System.Threading.ExecutionContext.Run(ExecutionContext
executionContext, ContextCallback callback, Object state)
at
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object
state)
"


Is this saying that line 94 and 283 are conflicting?
94 is in Draw_sonar2 and 283 is calling Draw_sonar2. Is it a case
where a function is getting called again before it is finished?

Any ideas on how to fix it. It happens every few minutes, not all the
time.

Here is all the code from the form. I'll put the line numbers next to
those 2 lines.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace EzExpander
{
public partial class Form1 : Form
{
private System.Drawing.Bitmap m_objDrawingSurface;
SensorStruct SA = new SensorStruct();
TextBox[] Sonar = new TextBox[10];
Label[] SonarNum = new Label[10];
int robotDiameter = 50;

public Form1()
{
InitializeComponent();
}

private void button_test_Click(object sender, EventArgs e)
{
Graphics objGraphics;
Rectangle rectBounds;
int robotDiameter=50;
//int BeamWidth = 15;//degrees
//double Range = 100;//cm
int startX, startY;//, endX, endY;
//int angle=270;
//int xx, yy;
startX = m_objDrawingSurface.Width / 2 ;
startY = m_objDrawingSurface.Height - robotDiameter;

objGraphics = Graphics.FromImage(m_objDrawingSurface);
objGraphics.Clear(SystemColors.Control);
//draw robot
rectBounds = new Rectangle((m_objDrawingSurface.Width / 2)
- (robotDiameter/2), m_objDrawingSurface.Height- (robotDiameter),
robotDiameter,robotDiameter);
rectBounds.Inflate(-1, -1);
objGraphics.DrawEllipse(Pens.Orange, rectBounds);
for (int i = 0; i <= 9; i++)
{
SA.Range = 100*int.Parse(textBox_scale.Text);
}

for (int i = 0; i <= 9; i++)
{

DrawSonar(SA.sensorAngle,SA.sensorBeamWidth,SA.Range);
Sonar.Text = SA.Range.ToString();
}

objGraphics.Dispose();
PB.Refresh();
}


private void DrawSonar(int Angle, int BeamWidth, double Range)
{
Graphics objGraphics;
// Rectangle rectBounds;
int startX, startY, endX, endY;
int angle;
startX = m_objDrawingSurface.Width / 2;
startY = m_objDrawingSurface.Height - robotDiameter/2;

objGraphics = Graphics.FromImage(m_objDrawingSurface);
for (int x = 0; x <= 9; x++)
{
if (Range <= int.Parse(textBox_maxRange.Text))
for (angle = (Angle - (BeamWidth / 2)); angle < (Angle
+ (BeamWidth / 2)); angle += 2)
{
endX = startX + (int)(Range * Math.Cos(angle *
6.28 / 360));
endY = startY + (int)(Range * Math.Sin(angle *
6.28 / 360));
objGraphics.DrawLine(Pens.Green, startX, startY,
endX, endY);

}
}

objGraphics.Dispose();
PB.Refresh();
PB.Invalidate();
}


private void DrawSonar2()
{
Graphics objGraphics;
objGraphics = Graphics.FromImage(m_objDrawingSurface);
objGraphics.Clear(SystemColors.Control);
int startX, startY, endX, endY;
int angle;
startX = m_objDrawingSurface.Width / 2; //line 94
startY = m_objDrawingSurface.Height - robotDiameter/2;

for (int x = 0; x <= 9; x++)
{
int Angle = SA.sensorAngle[x];
int BeamWidth = SA.sensorBeamWidth[x];
int Range = SA.Range[x]*int.Parse(textBox_scale.Text);
if (Range > int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text))
Range = int.Parse(textBox_maxRange.Text) *
int.Parse(textBox_scale.Text);
for (angle = (Angle - (BeamWidth / 2)); angle < (Angle
+ (BeamWidth / 2)); angle += 2)
{
endX = startX + (int)(Range * Math.Cos(angle *
6.28 / 360));
endY = startY + (int)(Range * Math.Sin(angle *
6.28 / 360));
objGraphics.DrawLine(Pens.Green, startX, startY,
endX, endY);
}
}

objGraphics.Dispose();
PB.Invalidate();
}
private void PB_Paint(object sender, PaintEventArgs e)
{
/////////////////////////////////
Graphics objGraphics;
objGraphics = e.Graphics;
// objGraphics.Clear(SystemColors.Control);

objGraphics.DrawImage(m_objDrawingSurface, 0, 0,
m_objDrawingSurface.Width, m_objDrawingSurface.Height);
// objGraphics.Dispose();
}

private void InitializeSurface()
{
Graphics objGraphics;
Rectangle rectBounds;
objGraphics = Graphics.FromImage(m_objDrawingSurface);
objGraphics.Clear(SystemColors.Control);
rectBounds = new Rectangle(0, 0,
m_objDrawingSurface.Width, m_objDrawingSurface.Height);
rectBounds.Inflate(-1, -1);
//objGraphics.DrawEllipse(Pens.Orange, rectBounds);

objGraphics.Dispose();


}


private void Form1_Load(object sender, EventArgs e)
{
SA.Range = new int[16];
SA.sensorAngle = new int[16];
SA.sensorBeamWidth = new int[16];
SA.sensortype = new int[16];

initialize_Sensors();

m_objDrawingSurface = new
Bitmap(PB.Width,PB.Height,System.Drawing.Imaging.PixelFormat.Format24bppRgb);
InitializeSurface();
sp.BaudRate = 19200;
// sp.PortName = "COM44";
sp.DataBits = 8;
sp.StopBits = System.IO.Ports.StopBits.One;
sp.Parity = System.IO.Ports.Parity.None;
sp.Encoding = Encoding.ASCII;

sp.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(sp_DataReceived);

string[] ports =
System.IO.Ports.SerialPort.GetPortNames();// sp.GetPortNames();
Console.WriteLine("The following serial ports were
found:");
foreach (string port in ports)
{
comboBox1.Items.Add(port);
Console.WriteLine(port);
comboBox1.Text = port;
}
}


public void initialize_Sensors()
{
int x;

for (x = 0; x <= 9; x++)
{
SA.sensorBeamWidth[x] = 15;
SA.sensorAngle[x] = 360-(x * 20);
SA.sensortype[x] = 1;//sonar
SA.Range[x] = 0;
Sonar[x] = new TextBox();
Sonar[x].Visible = true;
this.Controls.Add(Sonar[x]);
Sonar[x].Left = 587;
Sonar[x].Top = 165+(x * 25);
Sonar[x].Width = 50;

SonarNum[x] = new Label();
SonarNum[x].Visible = true;
this.Controls.Add(SonarNum[x]);
SonarNum[x].Left = 537;
SonarNum[x].Top = 165 + (x * 25);
SonarNum[x].Width = 50;
SonarNum[x].Text = "sonar" + x;

this.Visible = true;
}
for (x = 10; x <= 15; x++)
{
SA.sensorBeamWidth[x] = 3;
SA.sensorAngle[x] = 360-(x * 20);
SA.sensortype[x] = 2;//IR
SA.Range[x] = 0;
}
}


void sp_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
//received data looks like this
//0 159 1 154 2 156 3 159 4 152 5 159 6 155 7 155 8 151 9
153
// sensor_number range Sensor_number range....
string Mystring=sp.ReadLine();
int l = Mystring.Length;
Console.WriteLine("Received:{0} bytes {1}", l,Mystring);
int r=0;
int SensorNumber = 0;
int lowvalue=0;
int highvalue = 0;
int combinedvalue=0;

if (l >= 1)
{
if (radioButtonDirectConnect.Checked == true)
{
// line contains ten values, looks like this
// 16 34 0 69 45 15 7 6 5 6
// Console.WriteLine("direct connect");
SensorNumber = 0;
Mystring = Mystring.Trim();
// Mystring = Mystring.Replace('>', ' ');
string[] ranges = Mystring.Split(' ');
foreach (string range in ranges)
{
try
{
r = Convert.ToInt16(range);
// Console.WriteLine("Range: {0}", r);
SA.Range[SensorNumber] = r;
SensorNumber++;
}
catch(System.FormatException)
{

}
finally
{

}
}
} // end of direct connect
else //I2C
{
// line contains 20 8-bit values, Need to be
turned into 10 16bit
SensorNumber = 0;
Mystring = Mystring.Trim();
string[] ranges = Mystring.Split(' ');
for (int x = 0; x < ranges.Length; x += 2)
{
try
{
lowvalue = Convert.ToInt16(ranges[x]);
highvalue = Convert.ToInt16(ranges[x + 1]);
combinedvalue = ((highvalue * 255) +
lowvalue);
Console.WriteLine("Range: {0}",
combinedvalue);
SA.Range[SensorNumber] = combinedvalue;
SensorNumber++;
}
catch (System.FormatException)
{

}
finally
{

}
}
}

}
DrawSonar2(); //line 283
// PB.Invalidate();

}


private void PB_Click(object sender, EventArgs e)
{

}

private void button_stream_Click(object sender, EventArgs e)
{
// sp.Write("fw\r");
//sp.Write("f");
//System.Threading.Thread.Sleep(1);
//sp.Write("w");
//System.Threading.Thread.Sleep(1);
//sp.Write("\r");
//System.Threading.Thread.Sleep(2000);
sp.Write("streams\r");
//int delaytime = 10;
//sp.Write("s");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("t");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("r");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("e");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("a");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("m");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("s");
//System.Threading.Thread.Sleep(delaytime);
//sp.Write("\r");



}

private void timer1_Tick(object sender, EventArgs e)
{
for (int i = 0; i <= 9; i++)
Sonar.Text = SA.Range.ToString();

}

private void button_open_serial_port_Click(object sender,
EventArgs e)
{
sp.PortName = comboBox1.Text;
// sp.ReadTimeout = 0;
sp.Open();
}

private void vScrollBar1_Scroll(object sender, ScrollEventArgs
e)
{
textBox_scale.Text = vScrollBar1.Value.ToString();
}

private void radioButtonI2C_serializer_CheckedChanged(object
sender, EventArgs e)
{

}

private void button_readI2C_Click(object sender, EventArgs e)
{
sp.WriteLine("i2c r 100 20\r");
}

private void button_reset_Click(object sender, EventArgs e)
{
sp.Write("reset\r");
}

private void button_stop_Click(object sender, EventArgs e)
{
sp.Write("stop\r");
}

private void button_send_delay_Click(object sender, EventArgs
e)
{
sp.Write("delay " + tb_delay.Text + "\r");
}
}
}
 
P

Peter Duniho

Ringo said:
[...]
I commented out the line that erases the background, still get the
error.

My point wasn't that the background erasure was causing the error. It
was that it was superfluous in a specific situation.
Here is the exact error
"System.InvalidOperationException was unhandled
Message="Object is currently in use elsewhere."
Source="System.Drawing"
[...]

Is this saying that line 94 and 283 are conflicting?

No. That wouldn't make any sense. You only get to line 94 because of
the call to the method at line 283. They don't conflict...they just
happen to be in the same call stack, and they are in the same call stack
because one calls the other.
94 is in Draw_sonar2 and 283 is calling Draw_sonar2. Is it a case
where a function is getting called again before it is finished?

Sounds more like you are trying to use the same Bitmap object from two
different threads. If you were only drawing from a single thread, that
wouldn't be an issue.
Any ideas on how to fix it. It happens every few minutes, not all the
time.

Well, your original attempt, fixed with my suggestions, should suffice.
Then you don't even need the Bitmap object, so accessing it
simultaneously from different threads becomes a non-issue. Of course,
then you'd need to synchronize access to the data used for drawing
instead. Either way, you have some synchronization to deal with.

As I mentioned it would be less efficient to draw everything from
scratch in the Paint handler anyway. A different way to fix it would be
to synchronize access to the Bitmap object. You can do this with the
lock() statement (preferably creating a dummy object instance that goes
with the Bitmap instance, rather than locking the Bitmap itself). Then
you'd have both the drawing code and the update code use the lock()
statement to protect the sections of code using the Bitmap.

If you are uncertain about using the lock() statement, a very simple
alternative that would fix all of the problems would be to do the
drawing as you were originally, but to use the Invoke() method in your
data-receiving thread to call the DrawSonar2() method. This will ensure
that only one thread is ever accessing the Bitmap object, while still
allowing the data-receiving thread to manage the order of operations for
the use of the object.

An alternative to that would be to create a copy of the data and pass
that to the DrawSonar2() method using BeginInvoke() instead of Invoke().
Then the data-receiving thread doesn't have to wait for the bitmap to
be updated, but you still ensure that no data is shared between the
threads without synchronization.

There are a number of other options too, but I think you should pick one
of the above and go with it. :)

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top