Nice3point / RevitToolkit

Toolkit for Revit plugin development
MIT License
81 stars 12 forks source link

Update Progress bar using BackgroundWorker and ActionEventHandler #4

Closed AZnait closed 2 months ago

AZnait commented 5 months ago

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();
}
Nice3point commented 5 months ago

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();
AZnait commented 5 months ago

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();
}
Nice3point commented 5 months ago

You can then move SignalEvent

eventHandler.Raise(application => 
{
   ExcuteEventClass.ExcuteMyEvent()
   ExcuteEventClass.SignalEvent.WaitOne();
   ExcuteEventClass.SignalEvent.Reset();
});

inside the lambda Raise() method

AZnait commented 5 months ago

Doing this causes the same behavior as raising the event Asynchronously, the worker completes its work before going through the code block.

Nice3point commented 5 months ago

I can recommend only debugging, without debugging it is not obvious to understand the mechanics of this code functioning