I'm creating a custom add-in command through the API of a piece of architectural modeling software called Revit. My command may take some time to complete so I want to show the user a window with a progress bar as it is working.
Typically if I were to create a progress window like this it would be on the main UI thread, and the actual work being done would happen on a secondary worker thread. However, Revit requires that any access to the API be through the thread calling the custom command. So I must create my progress bar on a second thread.
I found this blog post about launching a WPF window in a separate thread, and based my solution on it. Here's my custom command class.
public class SampleProgressWindowCommand : Autodesk.Revit.UI.IExternalCommand
{
private ProgressWindow progWindow;
internal static EventWaitHandle _progressWindowWaitHandle;
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
//Starts New Progress Window Thread
using (_progressWindowWaitHandle = new AutoResetEvent(false))
{
//Starts the progress window thread
Thread newprogWindowThread = new Thread(new ThreadStart(ShowProgWindow));
newprogWindowThread.SetApartmentState(ApartmentState.STA);
newprogWindowThread.IsBackground = true;
newprogWindowThread.Start();
//Wait for thread to notify that it has created the window
_progressWindowWaitHandle.WaitOne();
}
//Does some work that takes a long time
for (int i = 1; i <= 100; i++)
{
//Updates Progress
this.progWindow.UpdateProgress("Item " + i.ToString(), i, 100);
//Does some fake work
System.Threading.Thread.Sleep(700);
}
//closes the Progress window
progWindow.Dispatcher.Invoke(new Action(progWindow.Close));
//Show Result to User
Autodesk.Revit.UI.TaskDialog.Show("Task", "Task Completed");
return Result.Succeeded;
}
private void ShowProgWindow()
{
//creates and shows the progress window
progWindow = new ProgressWindow();
progWindow.Show();
//makes sure dispatcher is shut down when the window is closed
progWindow.Closed +=new EventHandler(progWindow_Closed);
//Notifies command thread the window has been created
_progressWindowWaitHandle.Set();
//Starts window dispatcher
System.Windows.Threading.Dispatcher.Run();
}
}
And here is the UpdateProgress() method on my ProgressWindow class
public void UpdateProgress(string message, int current, int total)
{
this.Dispatcher.Invoke(new Action<string, int, int>(
delegate(string m, int v, int t)
{
this.progressBar1.Maximum = System.Convert.ToDouble(t);
this.progressBar1.Value = System.Convert.ToDouble(v);
this.messageLbl.Content = m;
}),
System.Windows.Threading.DispatcherPriority.Background,
message, current, total);
}
My first question is in general did I do this right? It seems to work, but I know enough about multithreaded programing to know that just because it works today, does not mean it's going to work tomorrow.
Second, I would like to add a cancel button to my progress window to be able to cancel the process. What is the best way to do this? I understand that ultimately I'll end up with a "cancelRequested" boolean flag that is checked regularly by the working thread, but how do I set this from the progress window thread?
The only improvement that I can see is that you have a potential race condition between setting your AutoResetEvent
and calling Dispatcher.Run
. I know because I've run into this issue in my own use of multi-threaded progress UIs.
The way to fix it is to BeginInvoke
the call on the background Dispatcher
. This will ensure it executes after the Dispatcher
has begun pumping events:
System.Windows.Threading.Dispatcher.Current.BeginInvoke(
new Func<bool>(_progressWindowWaitHandle.Set));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With