Having trouble creating a form on a second thread
I'm writing a plug-in for another application through C#.NET. Some of the processes my plug-in must perform are rather time consuming so I want to take advantage of multiple threads so I can show the user a progress bar of how the current task if progressing rather then the whole thing just hanging.
Typically the UI for something like this would be created in the main thread, and a secondary thread would be created to do the work, such as through the BackGroundWorker class. However, in my case the work must be done in the main thread because the application I'm writing t开发者_运维百科he plug-in for isn't to happy with threads other then the thread it created for the plug-in accessing it.
So instead I'm creating a second thread to create my UI in (a WinForms Form), which then communicates back to the main thread to do any real work.
I'm able to create my Form in the main thread just fine, yet when I try to instantiate my form in the second thread I get an InvalidOperationException. This occurs in the designer file for the form where the name property of a column in a list view is being set.
Here are the details of the exception.
System.InvalidOperationException was caught
Message=ColumnInfo cannot be set.
Source=System.Windows.Forms
StackTrace:
at System.Windows.Forms.ListView.SetColumnInfo(Int32 mask, ColumnHeader ch)
at System.Windows.Forms.ColumnHeader.set_Text(String value)
at QA.Revit.RevitQAForm.InitializeComponent() in C:\Documents and Settings\eric.anastas\My Documents\_SVN WC\QA Tool\RevitModelCheckerPlugIn\RevitQAForm.Designer.cs:line 758
at QA.Revit.RevitQAForm..ctor() in C:\Documents and Settings\eric.anastas\My Documents\_SVN WC\QA Tool\RevitModelCheckerPlugIn\RevitQAForm.cs:line 34
at QA.Revit.RevitQAToolApp.FormMethod() in C:\Documents and Settings\eric.anastas\My Documents\_SVN WC\QA Tool\RevitModelCheckerPlugIn\RevitModelCheckerCmd.cs:line 99
InnerException:
Update
I seemed to have gotten this working now by changing the ApartmentState of the secondary UI thread to STA. Although I'm totaly new to this multithreading stuff and have no idea what ApartmentState or STA means.
Here's my code.
//property used to store a reference to the form
internal RevitQAForm RevitQAForm { get; set; }
//monitor object that when pulsed shows the form
public static readonly Object showFormLock = new object();
//this method is called by the parent app when it starts
public Autodesk.Revit.UI.Result OnStartup(Autodesk.Revit.UI.UIControlledApplication application)
{
//this creates the form UI Thread
_formThread = new System.Threading.Thread(new System.Threading.ThreadStart(FormMethod));
_formThread.Name = "Form Thread";
_formThread.SetApartmentState(System.Threading.ApartmentState.STA);
_formThread.Start();
//returns that the plug-in startup succeeded
return Autodesk.Revit.UI.Result.Succeeded;
}
//the method is started on the second thread
private void FormMethod()
{
try
{
//creates the form
RevitQAForm = new RevitQAForm();
lock (showFormLock)
{
while (true)
{
//waits for a pulse
System.Threading.Monitor.Wait(showFormLock);
RevitQAForm.ShowDialog();
}
}
}
catch (System.Threading.ThreadAbortException)
{
//disposes the form if the thread is aborted
RevitQAForm.Dispose();
}
}
//this is called when the user request the form be shown
public void ShowForm()
{
lock (showFormLock)
{
System.Threading.Monitor.Pulse(showFormLock);
}
}
//this is called when the program closes
public Autodesk.Revit.UI.Result OnShutdown(Autodesk.Revit.UI.UIControlledApplication application)
{
//aborts the form thread
formThread.Abort();
return Autodesk.Revit.UI.Result.Succeeded;
}
Like I said this seems to work now. I'm able to start the app with my plug-in and show the form repeatedly. The form is also disposed when I close the program.
Yet now I'm trying to figure out how this form can communicate back to the main thread. The form will need to be able to trigger the main thread to start processing, the main thread will then need to be able to periodically report it's progress back to the form thread. At any point the form thread should be able to tell the main thread to cancel processing. Finally the main thread will need to notify the form when the processing is complete.
Any one have any tips on how I could do this?
This won't work. All forms need to use the underlying message pump in Windows, and to do that they need to be on the original thread.
To trigger the processing in main thread, you can use any WaitHandle derived class such as say ManualResetEvent/AutoResetEvent - essentially, Main thread will wait on to the wait handle and form thread can signal the event to start processing.
For communicating progress back from main thread to your UI/Form thread, you can use events or delegates. The simplest way would be to declare the process update delegate, instantiate it with some form's method. Then main thread can invoke it - which will essentially run the method within your form class (on main thread). Within this method, you must need to marshall call to your form's thread using Invoke method the form.
Try to call method, which uses
System.Windows.Forms.ListView.SetColumnInfo(Int32 mask, ColumnHeader ch)
by using method Invoke.
精彩评论