Reputation: 47
I have an object running it's own thread, processing input from a webcam and updating a window Control. During it's operation a user may indicate they wish to calibrate the application where I would essentially like to have the current thread suspend, open a new Form in its own thread, have the user perform their inut and click OK whereupon this temp thread dies and the original resumes. My calibration Form looks like
//Constructor() {}
public void StartCalibration() {
Debug.WriteLine("StartCalibration CALLED!");
var thread = new Thread(Run);
thread.Start();
}
private void Run() {
while (!finished) {
//process user input
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
Graphics g = e.Graphics;
if (image != null)
g.DrawImage(image, new Point(15, 15));
}
In the original objects thread I have the following
public void Run() {
if (calibrate == true) {
CalibrationForm calibrationForm = new CalibrationForm(source);
if (calibrationForm.InvokeRequired)
calibrationForm.Invoke(new MethodInvoker(calibrationForm.StartCalibration));
else
calibrationForm.StartCalibration();
if (calibrationForm.ShowDialog() == DialogResult.OK)
//get data from calibrationForm
calibrationForm.Dispose();
}
//continue with this threads operation
It looks like the call to ShowDialog() however is still throwing an exception,
Cross-thread operation not valid: Control 'CalibrationForm' accessed from a thread other than the thread it was created on
So I'm currently wondering whether my best option is to try and simply suspend my current thread and wait till the calibration Form finishes to attempt a join, or is there a better way to achieve my goal?
Upvotes: 0
Views: 5520
Reputation: 47
So I got this to work eventually, my main issue primarily being getting my second form to run it's own task and behave like a standard dialog window.
public partial class CalibrationForm : Form {
public CalibrationForm(ISource source) {
InitializeComponent();
this.source = source;
//other setup
}
private void Run() {
while (!finished) {
//process user input
this.Invalidate();
if (this.InvokeRequired)
this.BeginInvoke(new MethodInvoker(Update));
else
Update();
}
}
private void OKButton_Click(object sender, EventArgs e) {
finished = true;
}
And then in the calling thread...
CalibrationForm calibrationForm = new CalibrationForm(camera);
new Thread(calibrationForm.Run).Start();
if (calibrationForm.ShowDialog(this) == DialogResult.OK)
//get data from calibrationForm
Last thing was to make sure to set the DialogResult properties for the buttons on the form so they would return the appropriate value to the ShowDialog()
call.
Upvotes: 0
Reputation: 2256
As others have said, only the main UI thread can interact with UI components.
To interact with the UI from other threads, you have 2 options. You can either call Control.Invoke or Control.BeginInvoke on a Form/Control or use a WindowsFormsSynchronizationContext. Example of WindowsFormsSynchronizationContext
Upvotes: 1
Reputation: 613461
You need to run all code interacts with your UI on the UI thread. That's a hard rule of WinForms.
If you need to show UI, or interact with existing UI, from your thread, call either Invoke or BeginInvoke depending on whether you want synchronous or asynchronous operation. These methods will execute the UI code on the UI thread.
Upvotes: 0
Reputation: 33149
In WinForms, all UI elements must be accessed from the main thread.
You can do background processing on other threads (such as retrieving data from a remote source, or logging) but you cannot create or modify UI elements.
Upvotes: 2