Update Progress bar using BackgroundWorker and ActionEventHandler
At first, thank you for taking the time and effort to create such a helpful toolkit. I have a question rather than an Issue. I'm trying to update a WPF progress bar through a long-running process that is happening inside an ActionEventHandler which is raised inside a ViewModel and all running with no problems except that the progress gets updated at the end and not incrementally. Here is a sample of my code. Any pointers of how I can get it working would be very helpful.
Regards, Ahmad
public partial class ViewModel: ObservableObject
{
private BackgroundWorker worker;
private readonly ActionEventHandler eventHandler;
[ObservableProperty]
private int currentProgress;
[RelayCommand]
private void RunWorker()
{
CurrentProgress = 0;
if (!worker.IsBusy)
{
worker.RunWorkerAsync();
}
}
public ViewmModel(UIApplication uIApplication, Window window)
{
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
worker.ProgressChanged += Worker_ProgressChanged;
eventHandler= new();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
ExcuteEventClass.Worker=worker;
eventHandler.Raise(application => ExcuteEventClass.ExcuteMyEvent());
ExcuteEventClass.SignalEvent.WaitOne();
ExcuteEventClass.SignalEvent.Reset();
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Message.Display("Worker done", WindowType.Information);
}
private void Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.CurrentProgress = e.ProgressPercentage;
}
-------- ExcuteEventClass-------
public static BackgroundWorker Worker { get; set; }
public static ManualResetEvent SignalEvent = new ManualResetEvent(false);
public static void ExcuteMyEvent()
{
int i=0
foreach(....)
{
i++
worker.ReportProgress(i)
}
SignalEvent.Set();
}
ActionEventHandler is executed after ExcuteEventClass.SignalEvent.WaitOne(). use AsyncEventHandler and await/async keywords
await eventHandler.RaiseAsync(application => ExcuteEventClass.ExcuteMyEvent());
ExcuteEventClass.SignalEvent.WaitOne();
ExcuteEventClass.SignalEvent.Reset();
Unfortunately, using the AsyncEventHandler the worker completes before even going through the event and when the ReportProgress is called it throws Operation already completed exceptiopn.
private async Task Worker_DoWork(object sender, DoWorkEventArgs e)
{
ExcuteEventClass.Worker=worker;
await eventHandler.RaiseAsync(application => FindMyRoomUsingSpaceEvent.ExcuteFindMySpaceEvent());
ExcuteEventClass.SignalEvent.WaitOne();
ExcuteEventClass.SignalEvent.Reset();
}
You can then move SignalEvent
eventHandler.Raise(application =>
{
ExcuteEventClass.ExcuteMyEvent()
ExcuteEventClass.SignalEvent.WaitOne();
ExcuteEventClass.SignalEvent.Reset();
});
inside the lambda Raise() method
Doing this causes the same behavior as raising the event Asynchronously, the worker completes its work before going through the code block.
I can recommend only debugging, without debugging it is not obvious to understand the mechanics of this code functioning
Late answer ... sorry.
To have a ProgressBar updating during a long addin operation, you have these solutions (at least) :
- Use WinForm instead of WPF to display the progress dialog.
WinForm's Form class has the
Refresh()command that forces the UI update. But this solution is not perfect :
- you cannot have an "infinite" progress with smooth update.
- you must call
Refresh()explicitally, so the long operation has to be "decomposable" into smaller parts.
- Use WPF, but create the progress dialog in another STA thread.
WPF allows you to create the dialog (Window + ProgressBar) from a non UI thread (explicitly marked as STA).
This independent thread has it own message pump, and the progress will update smoothly, even in infinite mode.
You can call
ShowDialog()when showing the progress dialog (inside the new thread), so that Revit will be unreachable to the user during whole operation. To pilot the ProgressBar, you will have to useProgressBar.Dispatcher.BeginInvoke(...)To wait until the ProgessDialog is shown, you canWaitOne()on aManualResetEventthat isSet()inside theProgressDialog.Loadedevent.
Something like :
public static void ShowDialog(Action action)
{
var sync = new ManualResetEvent(false);
var thread = new Thread(() =>
{
Dialog = new();
Dialog.Loaded += (s, e) => sync.Set();
Dialog.ShowDialog();
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();
sync.WaitOne();
try
{
action();
}
finally
{
Dialog.Dispatcher.BeginInvoke(() => Dialog.Close());
}
}
private static ProgressDialog Dialog;
sealed class ProgressDialog : Window
{
...
}