I have some bugs that need fixing, one of them involves an out of memory error.
Does anyone know how to do this properly? Thanks, I don't want it to be too messy, or too complicated. I just want to treat a new image as a buffer to render another image to (because of positional changes), and do it via a background thread. Not the UI thread (Too slow likely).
I get out of memory errors, and such. Also not able to access members of Form1 from within the thread function (images and the like throw access errors such as "Object already in use")
Here is my code:
System.Threading.Thread t;
public Image b;
public Bitmap c;
public Bitmap d;
public Bitmap e;
public Bitmap bg;
public Bitmap spr;
int spritex = 0;
int spritey = 0;
int spritedir = 1;
public Form1()
{
InitializeComponent();
Text = "Escape The Hypno Mansion!!".ToString();
t = new System.Threading.Thread(DoThisAllTheTime);
t.Start();
textBox1.Text = "Press Begin button to start!";
pictureBox1.Image = Image.FromFile(#"Images\introgirl.jpg");
b = new Bitmap(#"Images\introgirl.jpg");
c = new Bitmap(#"Images\sprite.png");
var graphics = Graphics.FromImage(b);
Pen blackpen = new Pen(Color.Black, 3);
graphics.DrawLine(blackpen, 0, 0, 100, 100);
graphics.DrawImage(c, new Point(500, 500));
pictureBox1.Image = b;
//pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
public void DoThisAllTheTime()
{
while (true)
{
Point p = new Point(spritex, spritey);
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
using (var graphics = Graphics.FromImage(bg))
{
graphics.DrawImage(spr, p);
}
if (pictureBox1.Image != null)
{
pictureBox1.Image.Dispose();
}
pictureBox1.Image = bg;
pictureBox1.Invalidate();
if (spritedir == 1) { spritex += 5; }
if (spritedir == 2) { spritex -= 5; }
if (spritex < 0) { spritex = 0; spritedir = 1; }
if (spritex > 700) { spritex = 700; spritedir = 2; }
}
}
The reason you can't change the image in your picturebox is because the thread that created the image is not the thread that created the picturebox.
In a debugger you can check this by asking the picturebox for InvokeRequired (function Control.IsInvokeRequired) just before changing the function.
So let's rewrite your function and show that modern classes Like Task are much easier to use the your thread.
I'll start your task when the form is loading, and try to stop it when the form is closing.
private Task myTask = null;
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
private void OnFormLoading(object sender, EventArgs e)
{
// Start the task
this.myTask = Task.Run( () => DoMyWork(this.cancellationTokenSource.Token));
}
private void OnFormClosing(object sender, FormClosingEventArgs e)
{
// if the Task is still running, ask it to stop itself:
if (myTask != null && !myTask.IsCompleted)
{ // ask the task to stop and wait until it is completed:
this.cancellationTokenSource.Cancel();
// all Tokens extractes from this source will get state CancellationRequested
// wait maximum 5 seconds until the task is completed:
this.UseWaitCursor = true;
this.myTask.Wait(TimeSpan.FromSeconds(5));
this.UseWaitCursor = false;
// cancel closing if the task is still not completed
e.Cancel = !this.myTask.Completed;
}
}
Now the function DoMyWork:
private void DoMyWork(CancellationToken cancellationToken)
{
// Do the same as in your DoThisAllTheTime
// except that you regularly check cancellationToken.IsCancelRequested:
while(!cancellationToken.IsCancelRequested)
{
// calculate the image to display
var imageToDisplay = ...
this.DisplayImage(imageToDisplay);
}
}
void DisplayImage(Image imageToDisplay)
{
if (this.pictureBox1.InvokeRequired)
{
this.Invoke(new MethodInvoker( () => this.DisplayImage(imageToDisplay)));
}
else
{
this.PictureBox1.Image = imageToDisplay;
}
}
See:
How to cancel a Task and its children
Use InvokeRequired with lambda expression
Dispose every disposable instances before the loop ends. Your memory leak is related with disposable items not being cleaned from memory, so you'll eventually run out of memory in your infinite loop.
At the very least, you'll want to dispose both bitmaps at the end of the loop:
bg = new Bitmap(#"Images\test.bmp");
spr = new Bitmap(#"Images\sprite.png");
Related
I'm trying to create a WPF application that takes a video and screenshots it every second while playing.
Code
public Form2(string url)
{
InitializeComponent();
axWindowsMediaPlayer1.URL = url;
new Thread(delegate(){
CheckFrame();
}).Start();
}
private void CheckFrame()
{
for (int i = 0; i < 100; i++)
{
Bitmap screenshot = new Bitmap(axWindowsMediaPlayer1.Bounds.Width, axWindowsMediaPlayer1.Bounds.Height);
Graphics g = Graphics.FromImage(screenshot);
g.CopyFromScreen(axWindowsMediaPlayer1.PointToScreen(
new System.Drawing.Point()).X,
axWindowsMediaPlayer1.PointToScreen(
new System.Drawing.Point()).Y, 0, 0, axWindowsMediaPlayer1.Bounds.Size);
pictureBox1.BackgroundImage = screenshot;
System.Threading.Thread.Sleep(1000);
}
}
When using the x y values of the media player itself I get the error
Additional information: Cross-thread operation not valid: Control 'axWindowsMediaPlayer1'
accessed from a thread other than the thread it was created on.
When using 0 as X/Y values so just 0px and 0px from the form point of view,it runs fine
In this case you can use Timer:
Timer tm = new Timer();
public Form2(string url)
{
InitializeComponent();
axWindowsMediaPlayer1.URL = url;
tm.Interval = 1000;
tm.Tick += tm_Tick;
tm.Start();
}
int i = -1;
void tm_Tick(object sender, EventArgs e)
{
if (++i < 100)
{
Bitmap screenshot = new Bitmap(axWindowsMediaPlayer1.Bounds.Width, axWindowsMediaPlayer1.Bounds.Height);
Graphics g = Graphics.FromImage(screenshot);
g.CopyFromScreen(axWindowsMediaPlayer1.PointToScreen(
new System.Drawing.Point()).X,
axWindowsMediaPlayer1.PointToScreen(
new System.Drawing.Point()).Y, 0, 0, axWindowsMediaPlayer1.Bounds.Size);
pictureBox1.BackgroundImage = screenshot;
}
else
{
i = -1;
tm.Stop();
}
}
Note: To take screenshot with your method, axWindowsMediaPlayer1 must visible on the screen.
I have a weird problem (possibly a threading issue) that has been troubling me. I would like to have a progress bar for a task that I run in Excel/VSTO, that is started by clicking a button on the ribbon.
Since all access to the Excel Object model must occur in the main thread, I show my progress form modally on a separate thread. This works great most of the time, but odd things happen when I switch apps or windows via the Windows task bar. For example, if I am in the middle of a run and Excel is maximized, and I click Chrome on my taskbar to give it focus, then I click back to Excel and click between open Excel workbooks in the taskbar previews, sometimes the UI will partially freeze in the progress bar.
In Excel 2007/2010, I can still see the progress bar being updated but I can't drag the toolbar or click the Cancel button; this is why I say the UI partially freezes. Similar stuff happens in Excel 2013/2016 but I haven't tested as much on them.
If Excel has focus and we don't switch to another window during the execution of the task, then the progress bars work perfectly.
Does anyone have an idea what could be going wrong?
Is there another way I can go about displaying the progress bar in Excel to give consistent and reliable behavior? Note the limitation that the actual task needs to run on the main Excel UI thread and that for a progress form to be interactive and display properly, it needs to be on a secondary UI thread. Because of this, solution like using a BackgroundWorker won't work.
class RunSampleProgressTask
{
private ProgressForm _form;
internal volatile bool CancelPending;
internal volatile AutoResetEvent SignalEvent = new AutoResetEvent(false);
internal RunSampleProgressTask()
{
//create a new workbook
Globals.ThisAddIn.Application.Workbooks.Add(Microsoft.Office.Interop.Excel.XlWBATemplate.xlWBATWorksheet);
int hwnd = GetHwnd();
var thread = new Thread(() => ShowProgressForm(hwnd));
thread.SetApartmentState(ApartmentState.STA);
thread.Priority = ThreadPriority.Highest;
thread.Name = "ProgressFormThread";
thread.IsBackground = true;
thread.Start();
SignalEvent.WaitOne();
//In SDI Excel a newly created workbook won't be shown until the function ends so we use a timer to show the new workbook before running Run()
if (IsSDI)
ExecuteTaskIn(200,Run);
else
Run();
}
internal static void ExecuteTaskIn(int milliseconds, Action action)
{
var timer = new System.Windows.Forms.Timer();
timer.Tick += (s, e) =>
{
((System.Windows.Forms.Timer)s).Stop(); //s is the Timer
action();
};
timer.Interval = milliseconds;
timer.Start();
}
//returns true if it is Excel 2013 or 2016 that use the new Single Document Interface
public static bool IsSDI
{
get
{
string appVersion = Globals.ThisAddIn.Application.Version;
return appVersion.StartsWith("15") || appVersion.StartsWith("16");
}
}
private void ShowProgressForm(int hwnd)
{
_form = new ProgressForm(this) {StartPosition = FormStartPosition.CenterScreen};
_form.ShowInTaskbar = false;
_form.FormBorderStyle = FormBorderStyle.FixedSingle;
_form.ShowDialog(new Win32Window(hwnd));
}
protected void ReportProgress(int percent)
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.SetProgress(percent)));
}
protected void CloseForm()
{
if (_form == null || !_form.IsHandleCreated) return;
_form.BeginInvoke(new Action(() => _form.Close()));
}
internal static int GetHwnd()
{
if (IsSDI)
{
var window = Globals.ThisAddIn.Application.ActiveWindow;
int hwnd = (int)window.GetType().InvokeMember("Hwnd", BindingFlags.GetProperty, null, window, null); //late binding call to get Window.Hwnd
return hwnd;
}
else
{
return Globals.ThisAddIn.Application.Hwnd;
}
}
private const int BufRowSize = 16384; //must be a factor of RowsPerPage
private const int RowsPerPage = 1048576;
private const int BufColSize = 10;
private const int Repetitions = 80;
internal void Run()
{
ReportProgress(0);
Globals.ThisAddIn.Application.ScreenUpdating = false;
Globals.ThisAddIn.Application.EnableEvents = false;
var buf = new string[BufRowSize, BufColSize];
//fill the buffer with sample data
int cnt = 0;
for (int i = 0; i < BufRowSize; i++)
for (int j = 0; j < BufColSize; j++)
buf[i, j] = "String" + (++cnt);
var workbook = Globals.ThisAddIn.Application.ActiveWorkbook;
var sheet = workbook.ActiveSheet;
int currRow = 1;
for (int i = 0; i < Repetitions; i++)
{
if (CancelPending)
{
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
return;
}
sheet.Range[sheet.Cells[currRow, 1], sheet.Cells[currRow + BufRowSize - 1, BufColSize]].Value2 = buf;
currRow += BufRowSize;
if (currRow > RowsPerPage)
{
currRow = 1;
sheet = workbook.Sheets.Add(Missing.Value, sheet);
}
int percent = 100 * (i + 1) / Repetitions;
ReportProgress(percent);
}
CloseForm();
Globals.ThisAddIn.Application.ScreenUpdating = true;
Globals.ThisAddIn.Application.EnableEvents = true;
}
}
public partial class ProgressForm : Form
{
private ProgressBar _progressBar;
private Label _lblStatus;
private Button _btnCancel;
private RunSampleProgressTask _task;
internal ProgressForm(RunSampleProgressTask task)
{
InitializeComponent();
this.Width = 320;
this.Height = 120;
this.ControlBox = false;
_task = task;
_progressBar = new ProgressBar(){Left=10, Top = 10, Width= 300, Height = 30};
_lblStatus = new Label(){Left = 10, Top = 50};
_btnCancel = new Button(){Text = "Cancel", Left = _lblStatus.Right + 10, Top = 50, Width = 100};
_btnCancel.Click += _btnCancel_Click;
this.Controls.Add(_progressBar);
this.Controls.Add(_lblStatus);
this.Controls.Add(_btnCancel);
this.Shown += ProgressForm_Shown;
}
void ProgressForm_Shown(object sender, EventArgs e)
{
_task.SignalEvent.Set();
}
void _btnCancel_Click(object sender, EventArgs e)
{
_task.CancelPending = true;
_lblStatus.Text = "Cancelling...";
}
internal void SetProgress(int percent)
{
_progressBar.Value = percent;
_lblStatus.Text = percent + "%";
}
}
public class Win32Window : IWin32Window
{
public Win32Window(int hwnd)
{
Handle = new IntPtr(hwnd);
}
public Win32Window(IntPtr handle)
{
Handle = handle;
}
public IntPtr Handle { get; private set; }
}
This is my code that gets executed:
OpenMyWindowInNewThread(1280, 1024, 5);
//do some stuff
CloseMyWindow();
And here are the methods:
public void OpenMyWindowInNewThread(int width, int height, int top)
{
thread = new Thread(x => OpenMyWindow(width, height, top));
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
public void CloseMyWindow()
{
if (myWindow != null)
{
myWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
myWindow.Close();
myWindow = null;
}));
}
}
private void OpenMyWindow(int width, int height, int top)
{
myWindow = new MyView();
myWindow.Closed += new EventHandler(MyWindow_Closed);
myWindow.Width = width;
myWindow.Height = height;
myWindow.Top = top;
myWindow.Left = 0;
myWindow.ShowDialog();
}
When I call the first code for the first time, the screen is shown and closes, as it should. However, when I execute the code for the second time, here's what happens: OpenMyWindowInNewThread() gets called first, then OpenMyWindow(), then the if condition in the CloseMyWindow() method, and AFTER THAT, the call of the MyView() constructor in the OpenMyWindow() method. So, the window never gets closed because first it's closed, and than it's created.
Why is this happening? Can someone explain?
The problem is that you have two threads accessing the window; the thread that closes the window is running the close method before the thread performing the open completes its operation. The simplest way to solve this is to lock the opening and closing of the window. As already mentioned, multiple UI threads are not an easy concept and it can get very, very messy!
private static object _syncLock = new object();
public void CloseMyWindow()
{
lock(_syncLock)
{
if (myWindow != null)
{
myWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
{
myWindow.Close();
myWindow = null;
}));
}
}
}
private void OpenMyWindow(int width, int height, int top)
{
lock(_syncLock)
{
myWindow = new MyView();
myWindow.Closed += new EventHandler(MyWindow_Closed);
myWindow.Width = width;
myWindow.Height = height;
myWindow.Top = top;
myWindow.Left = 0;
myWindow.ShowDialog();
}
}
I'm new at async world and i started developing some stuff for windows store apps.
My problem is that i need to wait the async method of filepicker ends to do other stuff.
So i tried search for: "run synchronous and asynchronous method c#" and i ended on Stephen Cleary blog. I tryed to use Nito AsyncEx to lead with my problem and it doesn't work because when i call the following method it doesn't stop.
private List<Templates> classModel = new List<Templates>();
private void btnLoadClassExercises_Click(object sender, RoutedEventArgs e)
{
AsyncContext.Run(() => loadFile());
// do some stuff with classModel
}
private async void loadFile()
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.FileTypeFilter.Add(".xml");
var file = await openPicker.PickSingleFileAsync();
classModel = xml.getControlClass(file);
}
During my search i found a classe called AsyncInLine that helped me out with some others stuffs like this one. But with this specific situation, i didn't work too. (http://social.msdn.microsoft.com/Forums/en-US/async/thread/163ef755-ff7b-4ea5-b226-bbe8ef5f4796)
Can someone help me?
Edit #1:
//When i click on this button, it loads the file and all canvas are drawn on canvasSegment.
private async void btnLoadClassExercises_Click(object sender, RoutedEventArgs e)
{
try
{
if (classModel != null)
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.FileTypeFilter.Add(".xml");
var file = await openPicker.PickSingleFileAsync();
classModel = xml.getControlClass(file);
auxCV = 0;
if (editClass)
CanvasSegment.Children.Clear();
foreach (Templates temp in classModel)
createCanvasSegment(temp);
editClass = true;
}
}
catch { }
}
// When i want to del some canvas, i put the removeCV = true and click on a canvas
private void btnDelExercise_Click(object sender, RoutedEventArgs e)
{
classEdit = classModel; //To debuf if the classModel.Count = 0
removeCV = true;
}
//When i click on a canvas, if removeCV = false, i draw the canvas on the main canvas(canvasClass), otherwise, i delete it. The problem is here. My modelClass.count here is = 0.
private void cv_PointerPressed(Templates temp, Canvas cv)
{
if (!removeCV)
{
foreach (StackPanel sp in CanvasSegment.Children)
foreach (Canvas c in sp.Children)
c.Opacity = 1;
cv.Opacity = 0.8;
canvasClass.Children.Clear();
for (int i = 0; i < temp.Shapes.Count; i++)
{
//Do some stuff with points and draws on CanvasClass later
rh.renderModel(new InkManager(), canvasClass, temp.Shapes[i], true, false);
}
}
else
{
for (int i = 0; i <= classModel.Count; i++)
if (classModel[i] == temp)
{
classModel.RemoveAt(i);
break;
}
CanvasSegment.Children.Clear();
auxCV = 0;
foreach (Templates tempModel in classModel)
createCanvasSegment(tempModel);
removeCV = false;
}
}
//I create a mini canvas foreach template in file.
private void createCanvasSegment(Templates temp)
{
Thickness thk = new Thickness();
thk.Right = 5;
thk.Bottom = 5;
if (temp != null || temp.Shapes != null)
{
if (auxCV == 0)
{
spcv = new StackPanel();
spcv.Orientation = Orientation.Horizontal;
spcv.Width = 540;
}
Canvas cv = new Canvas()
{
Background = new SolidColorBrush(Colors.Wheat),
Name = temp.Template,
Height = 73.125,
Width = 130,
Margin = thk,
};
cv.PointerPressed += (s, e) => cv_PointerPressed(temp, cv);
foreach (ShapePoints tempshapePoints in temp.Shapes)
{
ShapePoints tempS = tempShapePoints;
if (!removeCV)
{
//Do some stuff with points
}
rh.renderModel(new InkManager(), cv, tempS, true, false);
}
auxCV++;
spcv.Children.Add(cv);
if (auxCV == 1)
CanvasSegment.Children.Add(spcv);
else if (auxCV == 4)
auxCV = 0;
}
}
Since this is already in a UI thread's context, there's no need for AsyncContext.Run - just use:
private async void btnLoadClassExercises_Click(object sender, RoutedEventArgs e)
{
await loadFile());
// do some stuff with classModel
}
// Make this task returning (or even return the list instead of setting a local)
private async Task loadFile()
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.FileTypeFilter.Add(".xml");
var file = await openPicker.PickSingleFileAsync();
classModel = xml.getControlClass(file);
}
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.