Accessing control from another thread safely - c#

I'm creating a program that draws points in a bitmap in memory (using system.drawing) and displays it in a PictureBox control, this way :
private void button_startGen_Click(object sender, EventArgs e) {
continueTask_generate = true;
Task task = Task.Run(() => {
while(continueTask_generate) {
for (int i = 0; i < Iterations; i++) {
gm.GenerateNextPoint();
}
UpdateBitmap();
Thread.Sleep(Time);
}
});
}
private void UpdateBitmap() {
pictureBox_bitmap.Image = gm.bitmap;
}
The way this works is that, when you press the button "start generation", it'll start generating points, and you'll see that generation in the PictureBox. It generates i points, and then updates the pictureBox, waits for t milliseconds, and does the process again, until you press a "stop" button.
Now, as I'm updating the pictureBox control from a different thread than the main one, I'll get those pesky "InvalidOperationException". I'm a noob in threading. I've tried both solutions in this page but none of them worked for me.
(For reference, this is my 'updated' UpdateBitmap() method for the first solution, which it didn't work:
private delegate void UpdateBitmapDelegate();
private void UpdateBitmap() {
if(pictureBox_bitmap.InvokeRequired) {
UpdateBitmapDelegate deg = new UpdateBitmapDelegate(UpdateBitmap);
pictureBox_bitmap.Invoke(deg);
}
else {
pictureBox_bitmap.Image = gm.bitmap;
}
}
Any help would be appreciated, as I'm making this program mainly to learn this kind of thing.
Edit:
I'm not sure if the content of gm.GenerateNextPoint() is relevant, but I'll post it too. GenerateNextPoint() calls this method:
private void DrawPoint(Point point, int radius, Color color) {
SolidBrush brush = new SolidBrush(color);
using (Graphics g = Graphics.FromImage(bitmap)) {
if (radius > 0 && radius < 3) {
g.FillRectangle(brush, point.X, point.Y, radius, radius);
}
else if (radius > 1) {
int radiusOffset = (int)(radius / 2f);
g.FillEllipse(brush, point.X - radiusOffset, point.Y - radiusOffset, radius, radius);
}
}
}
This method also interacts with gm.bitmap, as does UpdateBitmap().

Pls try this:
Add this to your function on the other thread-
if (InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate
{ //your working code here }));
}
else
{
//the same code here
}

Related

Blank components in a Form while updating it from a thread

I have made a Form that moves across the screen to the left, but all of the components on the Form are blank.
I put the code for the movement in comments and everything was fine, so the problem is in my movement code, but I don't know what the problem is.
System.Threading.Thread thread;
private void Form1_Load(object sender, EventArgs e)
{
thread = new System.Threading.Thread(loop);
thread.Start();
}
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This should make the form move to the left, however the components on the Form are blank.
Let's look at the loop() method:
void loop()
{
this.BeginInvoke((Action)delegate () {
int x = 0;
int y = 0;
int MoveRate = 1;
Point TopLeft = this.Location;
Point TopRight = new Point (this.Location.X + this.Width, this.Location.Y);
while (true)
{
x = x + MoveRate;
this.Location = new Point(x, 150);
System.Threading.Thread.Sleep(10);
}
});
}
This code immediately invokes a delegate back on the main UI thread. The delegate runs a while(true) loop that never exits. As soon as the loop begins to execute, the UI thread is completely hosed with no hope of ever responding to other event messages, including paint events.
Try this instead:
void loop()
{
int x = 0;
int MoveRate = 1;
while(true)
{
x += MoveRate;
this.BeginInvoke((Action)delegate () { this.Location = new Point(x, 150); });
System.Threading.Thread.Sleep(16);
}
}
It's all the same code (except for the stuff that wasn't doing anything), but now arranged so the delegate is invoked inside the loop. The UI thread is only blocked for a brief time, and then control returns back to the loop thread, which will Sleep for a little while before bothering the UI again. The invoke even simple enough I was able to rewrite it as single line.
Notice I also increased the sleep time, because that still gives you 60 frames per second.
An Async variation of the same process, to test something different.
(The main reason why your thread wasn't working as expected has already been explained. If you use a thread and then invoke the UI thread in a close loop, it's more or less like not having your code run in a different thread at all: a Form doesn't have time to update itself or its controls).
This method add a termination to the scrolling procedure, when the Form is scrolled outside the current Screen bounds. When this condition is met, the while loop is exited and the Task ends, moving the Form in the center of the screen.
The Task is started in the Shown event. I think it's more appropriate than the Load event (the Form is ready to be presented, here).
Note that neither this Task or the Thread add any check on the Form.FormClosing event, to cancel the asynchronous proc: if the Form is closed while the scrolling is performed, you will most likely have a exception (the Form has been disposed, thus no more handle).
private async Task Scroller(int ScreenWidth)
{
int x = 0;
int MoveRate = 2;
while (true)
{
x += MoveRate;
this.BeginInvoke(new MethodInvoker(() => { this.Location = new Point(x, 150);}));
await Task.Delay(10);
if (x > ScreenWidth) break;
};
}
private async void Form_Shown(object sender, EventArgs e)
{
int ScreenWidth = Screen.FromHandle(this.Handle).Bounds.Width;
await this.Scroller(ScreenWidth);
this.Location = new Point((ScreenWidth - this.Width) / 2 , 150);
}

Why am I having trouble writing a pixel to a picturebox in Visual C#?

I am writing an emulator program, and the virtual display is supposed to be able to take in 3 bytes of color data and display the correct color pixel, similar to how a real screen works. But when I set up some scroll bars to test the generation of pixels nothing happens. Here is my code and a screenshot of the form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TSC_Multi_System_Emulator
{
public partial class Form1 : Form
{
private PictureBox Display = new PictureBox();
string #emulationfolderpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Bitmap screen = new Bitmap(#Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + #"\Resource_Folder\" + #"FirstFrame.bmp");
int x = 0;
int y = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, System.EventArgs e) {
// Dock the PictureBox to the form and set its background to black.
Display.BackColor = Color.Black;
// Connect the Paint event of the PictureBox to the event handler method.
// Add the PictureBox control to the Form.
this.Controls.Add(Display);
}
public void DigitalGraphicsDisplay(int red, int green, int blue) {
Graphics g = Display.CreateGraphics();
screen.SetPixel(x, y, Color.FromArgb(red, green, blue));
g.DrawImage(screen, 0, 0, screen.Width, screen.Height);
g.Save();
if (x < screen.Width)
{
x = x + 1;
}
else if (x == screen.Width)
{
x = 0;
if (y < screen.Height)
{
y = y + 1;
}
else if (y == screen.Height)
{
y = 0;
}
}
}
private void button1_Click(object sender, EventArgs e){
int rchannel = redControl.Value;
int gchannel = greenControl.Value;
int bchannel = blueControl.Value;
DigitalGraphicsDisplay(rchannel, gchannel, bchannel);
}
}
}
UPDATE:
The code is now working somewhat, but I can't test the code using just a test button. I had to use the exact code given to me in the first answer, which only displayed a gradient, I wonder what I am doing wrong... :(
public partial class Form1 : Form
{
string #emulationfolderpath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Bitmap screen = new Bitmap(#Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + #"\Resource_Folder\" + #"FirstFrame.bmp");
int x = 0;
int y = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, System.EventArgs e) {
// Dock the PictureBox to the form and set its background to black.
Display.BackColor = Color.Black;
// Connect the Paint event of the PictureBox to the event handler method.
// Add the PictureBox control to the Form.
this.Controls.Add(Display);
}
public void DigitalGraphicsDisplay(int red, int green, int blue)
{
if (Display.Image == null)
{
Bitmap NewBMP = new Bitmap(Display.ClientRectangle.Width, Display.ClientRectangle.Height);
using (Graphics g = Graphics.FromImage(NewBMP))
{
g.Clear(Color.White);
}
Display.Image = NewBMP;
}
(Display.Image as Bitmap).SetPixel(x, y, Color.FromArgb(red, green, blue));
Display.Invalidate();
x++;
if (x >= Display.Image.Width)
{
x = 0;
y++;
if (y >= Display.Image.Height)
{
y = 0;
}
}
}
private void button1_Click(object sender, EventArgs e){
Boolean a = false;
int b = 0;
do
{
DigitalGraphicsDisplay(51, 153, 102);
if (b == 10000)
{
a = true;
}
b = b + 1;
} while (a);
}
}
}
All I am getting is a white picturebox with nothing else in it...
(The gradient code did work though)
It looks like you are trying to draw directly on the PictureBox control itself.
Instead you should have an Image assigned to the PictureBox and then draw on the image.
Try changing your code as shown below. (Including the click event for testing.)
Note, the PictureBox keeps the reference to the image directly so you don't need a separate screen image in your class, unless you have a different purpose for it.
Also, this uses Bitmap.SetPixel() which is an extremely slow way to set pixels. There is a much faster but slightly more complex way, in these other links:
SetPixel is too slow. Is there a faster way to draw to bitmap?
Work with bitmaps faster in C#
Remember your button click will only draw one pixel at a time.
So be sure to look carefully:
Running my test code within the click event will yield this:
int x = 0;
int y = 0;
public void DigitalGraphicsDisplay(int red, int green, int blue)
{
if (Display.Image == null)
{
Bitmap NewBMP = new Bitmap(Display.ClientRectangle.Width, Display.ClientRectangle.Height);
using (Graphics g = Graphics.FromImage(NewBMP))
{
g.Clear(Color.White);
}
Display.Image = NewBMP;
}
(Display.Image as Bitmap).SetPixel(x, y, Color.FromArgb(red, green, blue));
Display.Invalidate();
x++;
if (x >= Display.Image.Width)
{
x = 0;
y++;
if (y >= Display.Image.Height)
{
y = 0;
}
}
}
private void button1_Click(object sender, EventArgs e)
{
// Temporary code to show that it works (Due to Bitmap.SetPixel() it will be slow)
for (int I = 1; I < Display.ClientRectangle.Width * Display.ClientRectangle.Height; I++)
DigitalGraphicsDisplay((I/255)%255, (I % Display.ClientRectangle.Width) % 255, 127);
}
UPDATE: Per your comment, try this sample code:
private void button1_Click(object sender, EventArgs e)
{
Boolean a = true;
int b = 0;
do
{
DigitalGraphicsDisplay(51, 153, 102);
if (b == 10000)
{
a = false;
}
b = b + 1;
} while (a);
}
public void DigitalGraphicsDisplay(int red, int green, int blue) {
Graphics g = Display.CreateGraphics();
screen.SetPixel(x, y, Color.FromArgb(red, green, blue));
g.DrawImage(screen, 0, 0, screen.Width, screen.Height);
g.Save();
All possible mistakes in one go..
Never use CreateGraphics to draw persistent Graphics! Always either go for the Paint event or draw into the Image.
Graphics.Save does not save any drawn pixels. It saves the state of the Graphics object, which does not contain graphics but is a tool to write into a related bitmap. The state includes scale, rotation, smoothing-mode and then some..
You already write into the Bitmap so you can simply make it your new PictureBox.Image..
Or the PictureBox.BackgroundImage.
And, as I said, you can instead write on top of both that is onto the PBox's surface. For this use the Paint event, Invalidate to trigger it and class level variables to hold the necessary data..
The latter is for graphics that will change a lot, the two former ones are for changes that accumulate.
Control.CreateGraphics is for transient graphics only, like a rubber-band line or a cursor cross..

Updating WPF GUI Every 2 Seconds (C#)

I'm doing an 8 Puzzle solver that ultimately stores each node (int[] of elements 0-8) in the path to put the blocks in order in a stack. I have a WPF GUI that displays an int[,]
foreach (var node in stack)
{
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
Thread.Sleep(500);
}
The GUI displays the initial board, and then after I click solve, the final (in order) board is displayed correctly. What I want to do is display each node on the board for some amount of time, ultimately arriving at the in-order board. With Thread.Sleep, the GUI will simply pause for the set amount of time before displaying the final node. Any ideas as to why it this code wouldn't display the board at each node every 500ms?
For reference, here's an example output from Console.Write for the nodes:
4,2,3,6,1,0,7,5,8
4,2,0,6,1,3,7,5,8
4,0,2,6,1,3,7,5,8
4,1,2,6,0,3,7,5,8
4,1,2,0,6,3,7,5,8
0,1,2,4,6,3,7,5,8
1,0,2,4,6,3,7,5,8
1,2,0,4,6,3,7,5,8
1,2,3,4,6,0,7,5,8
1,2,3,4,0,6,7,5,8
1,2,3,4,5,6,7,0,8
1,2,3,4,5,6,7,8,0
Edit:
Since my original answer was downvoted for using a Thread instead of a Timer, here is an example using a timer.
The code for using a Thread was just shorter and I wanted to give him a solution quickly.
Also, using a Thread instead of a timer meant he didn't need to pass parameters differently or restructure his loop.
This is why it is a good idea to discuss pros/cons of alternate solutions instead of simply insisting that there is only one right way.
Use the timer_Tick function to update the position.
You might notice that this complicates the original code since you will have to pass parameters differently and restructure your loop.
public partial class Form1 : Form
{
private Point pos = new Point(1,1);
private float[] vel = new float[2];
private Size bounds = new Size(20,20);
private Timer ticky = new Timer(); //System.Windows.Forms.Timer
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
ticky.Interval = 20;
ticky.Tick += ticky_Tick;
vel[0] = 4; vel[1] = 0;
ticky.Start();
}
void ticky_Tick(object sender, EventArgs e)
{
updatePosition();
//This tells our form to repaint itself (and call the OnPaint method)
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(new SolidBrush(Color.LightBlue), new Rectangle(pos, bounds));
}
private void updatePosition()
{
pos = new Point(pos.X + (int)vel[0], pos.Y + (int)vel[1]);
vel[1] += .5f; //Apply some gravity
if (pos.X + bounds.Width > this.ClientSize.Width)
{
vel[0] *= -1;
pos.X = this.ClientSize.Width - bounds.Width;
}
else if (pos.X < 0)
{
vel[0] *= -1;
pos.X = 0;
}
if (pos.Y + bounds.Height > this.ClientSize.Height)
{
vel[1] *= -.90f; //Lose some velocity when bouncing off the ground
pos.Y = this.ClientSize.Height - bounds.Height;
}
else if (pos.Y < 0)
{
vel[1] *= -1;
pos.Y = 0;
}
}
}
Results:
You can use timers to do all sorts of delayed form drawing:
Original Solution:
//Create a separate thread so that the GUI thread doesn't sleep through updates:
using System.Threading;
new Thread(() => {
foreach (var node in stack)
{
//The invoke only needs to be used when updating GUI Elements
this.Invoke((MethodInvoker)delegate() {
//Everything inside of this Invoke runs on the GUI Thread
int[,] unstrung = node.unstringNode(node); // turns node of int[] into board of int[,]
blocks.setBoard(unstrung); // sets the board to pass in to the GUI
DrawBoard(); // Takes the board (int[,]) and sets the squares on the GUI to match it.
});
Thread.Sleep(500);
}
}).Start();
Solution in 2022:
await Task.Delay(500);
Things really are better these days.

Calling a method from another class inside Panel_Paint does not draw anything

So this should be very simple but I have looked at some similar questions and can't find an answer.
I have a Form1 class and a Resistor class. Inside the Form1 class I have a Panel(I changed the name to Canvas), inside the Canvas_Paint method I am calling the method Draw from the Resistor class but is not drawing anything.
Form1 Class:
public partial class Form1 : Form
{
static float lineWidth = 2.0F;
static float backgroundLineWidth = 2.0F;
static Pen pen = new Pen(Color.Yellow, lineWidth);
static Pen backgroundPen = new Pen(Color.LightGray, backgroundLineWidth);
private bool drawBackground = true;
private List<Resistor> resistors = new List<Resistor>();
public Form1()
{
InitializeComponent();
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
if (drawBackground)
{
Console.WriteLine("Drawing background...");
Draw_Background(e.Graphics, backgroundPen);
}
if (resistors != null)
{
foreach (Resistor r in resistors)
{
//This does not work.
r.Draw(e.Graphics);
}
}
//The line below draws the line fine.
e.Graphics.DrawLine(pen, 0, 0, 100, 100);
}
private void Draw_Background(Graphics g, Pen pen)
{
for (int i = 0; i < Canvas.Width; i += 10)
{
g.DrawLine(pen, new Point(i, 0), new Point(i, Canvas.Height));
}
for (int j = 0; j < Canvas.Height; j += 10)
{
g.DrawLine(pen, new Point(0, j), new Point(Canvas.Width, j));
}
drawBackground = false;
}
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
}
}
Resistor Class:
public class Resistor
{
static private Point startingPoint;
static Pen defaultPen;
private Point[] points;
public Resistor()
{
startingPoint.X = 100;
startingPoint.Y = 100;
defaultPen = new Pen(Color.Yellow, 2.0F);
points = new Point[] {
new Point( 10, 10),
new Point( 10, 100),
new Point(200, 50),
new Point(250, 300)
};
}
public void Draw(Graphics g)
{
//Is this drawing somewhere else?
g.DrawLines(defaultPen, points);
}
}
I have looked at this question which suggests to pass the e.Graphics object in this case to the Draw method in the Resistor class but is not working.
I am new to C# so I would really appreciate any help.
EDIT :
I put the project on github if you want to download and try it out.
EDIT :
So the problem was that after clicking the button the panel Paint method was not being called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method
Run your code in the debugger and put a breakpoint in your event handler and you'll be able to check that your code is actually trying to draw something. If not, then is your event handler ever called? is there anything in your list of resistors? If it is drawing but you don't see anything, then you're not using the correct Graphics context or you're not drawing things in the visible part of your control, or you're drawing over things with subsequent drawing code.
The problem was that when the button was clicked the panel's paint method was not getting called because I tough that the paint method was always getting called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method.
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
Canvas.Invalidate();
}

Manipulating an image and updating the pictureBox has some issues

I could not fit exactly what I wanted to say in the title, it would be too long. Okay this is a multi-threaded app. What my app does is looks at a picture, find the edges of the picture, and finds the shape of that object from the edges. While it finds the shape, it constantly updates the image so we can get some sort of visual representation. I have created a very short (40 seconds) video demonstrating the issue: http://phstudios.com/projects/Programming/MultiThreadIssue/
As you can see, everything is working fine until the minute I move the window. This is always the case. I ran the program several times without moving the window and it ran fine. However, the minute I move the window, it will come up with that error. As you see, I am locking the specific image I would like to work with. Is the forms OnPaint overriding that somehow? Is there any way I can fix that?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;
namespace LineRecognition
{
public enum Shape
{
Unknown,
Quadrilateral,
Circle,
Triangle
}
public partial class Form1 : Form
{
Bitmap image, original, shapes;
List<Point> outlines;
Shape shape;
ShapeDetection detector;
public Form1()
{
InitializeComponent();
edgeDetection.WorkerReportsProgress = true;
shapeDetection.WorkerReportsProgress = true;
shape = Shape.Unknown;
}
private void Form1_Load(object sender, EventArgs e)
{
original = new Bitmap("photo1.png");
image = new Bitmap("photo1.png");
shapes = new Bitmap(image.Width, image.Height);
pictureBox1.Image = (Image)original;
}
private void findLines_Click(object sender, EventArgs e)
{
if (edgeDetection.IsBusy != true)
{
lblStatus.Text = "Finding Edges";
edgeDetection.RunWorkerAsync();
}
}
private void justTheOutlines(Bitmap image, List<Point> pixels, BackgroundWorker worker)
{
lock (image)
{
for (int i = 0; i < pixels.Count; i++)
{
image.SetPixel(pixels[i].X, pixels[i].Y, Color.Red);
worker.ReportProgress((int)((float)i * 100 / (float)pixels.Count));
}
}
}
private List<Point> outlineLines(Bitmap image, BackgroundWorker worker)
{
int w = image.Width;
int h = image.Height;
int alpha = 800000;
List<Point> changes = new List<Point>();
lock (image)
{
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color selected = image.GetPixel(i, j);
Color nextRight = selected;
Color nextDown = selected;
if (i < w - 1)
nextRight = image.GetPixel(i + 1, j);
if (j < h - 1)
nextDown = image.GetPixel(i, j + 1);
int iSelected = selected.ToArgb();
int iNextRight = nextRight.ToArgb();
int iNextDown = nextDown.ToArgb();
if (Math.Abs(iSelected - iNextRight) > alpha)
{
if (iSelected < iNextRight)
{
Point p = new Point(i, j);
if(!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i + 1, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
if (Math.Abs(iSelected - iNextDown) > alpha)
{
if (iSelected < iNextDown)
{
Point p = new Point(i, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i, j + 1);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
image.SetPixel(i, j, Color.White);
}
worker.ReportProgress((int)(((float)i / (float)w) * 100));
}
}
return changes;
}
private bool ContainsPoint(List<Point> changes, Point p)
{
foreach (Point n in changes)
{
if (n.Equals(p)) return true;
}
return false;
}
private void edgeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
outlines = outlineLines(image, worker);
justTheOutlines(image, outlines, worker);
pictureBox2.Image = (Image)image;
Thread.Sleep(100);
image.Save("photo-lines.jpg");
}
private void edgeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
private void edgeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
algorithmProgress.Value = 0;
findLines.Enabled = false;
determineShape.Enabled = true;
lblStatus.Text = "";
}
private void determineShape_Click(object sender, EventArgs e)
{
if (shapeDetection.IsBusy != true)
{
pictureBox1.Image = (Image)image;
lblStatus.Text = "Running Shape Detection: Circle -> Quadrilateral";
shapeDetection.RunWorkerAsync();
}
}
private void ShapeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
detector = new ShapeDetection(outlines, 40, 10);
detector.Worker = worker;
detector.circleChange += new ShapeDetection.CircleChangeEventHandler(circleChange);
if (detector.IsCircle())
{
MessageBox.Show("Object is a circle");
shape = Shape.Circle;
}
else if (detector.IsQuadrilateral())
{
MessageBox.Show("Object is a quadrilateral", "Number of edges: " + detector.Summits);
shape = Shape.Quadrilateral;
}
else
{
int sides = detector.Summits.Count;
if (sides == 3)
{
MessageBox.Show("Object is a triangle");
shape = Shape.Triangle;
}
else
{
MessageBox.Show("Number of edges: " + detector.Summits.Count, "Unknown");
}
}
BitmapDrawing.DrawLines(detector.Summits, shapes);
BitmapDrawing.DrawSummits(detector.Summits, shapes);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(100);
}
private void ShapeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (detector != null)
{
lblSummits.Text += detector.Summits.Count;
lblType.Text += shape.ToString();
determineShape.Enabled = false;
lblStatus.Text = "";
}
}
void circleChange(object sender, CircleChangeEventArgs e)
{
lock (shapes)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(10);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
}
private void shapeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
}
}
Update
What Nick said before worked fine. I added that to my CircleChange event and it works. Can somebody explain why the invoke makes it work instead of setting the picturebox2.Image to the shapes image? I mean I call it after I call setpixel, so I should be done modifying the image right?
void circleChange(object sender, CircleChangeEventArgs e)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
Image copyForPictureBox = shapes.Clone() as Image;
BeginInvoke(new Action(() => pictureBox2.Image = copyForPictureBox));
Thread.Sleep(15);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
It appears that you're operating on the shapes bitmap on a thread separate to the GUI thread. When you move the window the OnPaint routine will run which will also access the image.
What you need to do to solve this is operate on a separate bitmap in your worker thread, and then pass a copy of that to the GUI thread using Invoke on the form. That way you're guaranteed only to have one thread accessing the picture box image at a time.
Edit:
void MyThreadFunction( )
{
Bitmap localThreadImage;
...
Image copyForPictureBox = localThreadImage.Clone( ) as Image;
BeginInvoke( new Action( () => pictureBox.Image = copyForPictureBox ) );
....
}
So the idea is that you create a Bitmap on the thread which is only ever accessed by that thread (i.e. your background worker thread). And when you get to a point when you want to update the image in the PictureBox you invoke onto the the GUI thread using BeginInvoke (which doesn't block your worker thread) passing a copy of the Bitmap to the PictureBox.
Locking the shapes object at only one point in your application does not accomplish anything. You also use this bitmap to draw to the window, and my guess is that you are not locking it for drawing. You can either lock it in OnPaint as well, or use a different bitmap for manipulation and display.

Categories