Search code examples
c#delegatesprogress-barabstract

Update progres bar in main form from an abstract class and dynamic object


I tried many different ways to solve this problem, but I didn't find a solution. I tried to implement delegate and events, but they don't work in my case (I explain later why).

I've an application form (windows form - .NET Framework) where is instantiated a progress bar.

I need to update the progress bar based on the event trigger from an abstract class (example: there are many different button, with different functions, and i need to update the value of progress bar based on the button clicked).

The main problem is:

The functions linked to the button are inside an abstract class, which is instantiated dynamically:

public abstract class BaseClass
{
    public void FirstJob()
    ...

    public void SecondJob()
    ...
}

MainForm (where progressbar is located):

public partial class frmMain : MetroFramework.Forms.MetroForm
{
    private async void btnCorris_Click(object sender, EventArgs e)
    {
        // Dynamically instance an object
        string objectToInstantiate = $"{namespaces}.Proto.{btnCorry.Text}, Converx";
        var objectType = Type.GetType(objectToInstantiate);
        dynamic instantiatedObject = Activator.CreateInstance(objectType);

        await Task.Run(() =>
        {
            instantiatedObject.Generate(checkBoxClear.Checked); // this function should update the progressbar
        });
        
        ....
    }
    
    ...
    
}

Of course I can not directly refer to the progress bar from the BaseClass because there is a cross thread exception, and I can not use events because the class is abstract so I can not directly refer at the event inside of it...

I was thinking about create a class which is inherited from BaseClass but I don't think is the right way to do it, and since I am not that expert i tried at first to look around (even on ChatGPT) to find a solution ... then I tought about write here and ask at the experts, what is the correct way to implement it.


Solution

  • The typical way would be to give your method a Progress<T> object that it reports progress to. This takes care of the cross threading issue. So your code could look something like

    private async void btnCorris_Click(object sender, EventArgs e)
    {
        ...
        var progress = new Progress<double>();
        progress.ProgressChanged  += UpdateProgress;
        try{
            await Task.Run(() => instantiatedObject.Generate(checkBoxClear.Checked, progress ));
        }
        finally{ progress.ProgressChanged  -= UpdateProgress; }    
    }
    private void UpdateProgress(object sender, double v){
        // Update the progress bar
    }
    

    If you use a non modal progress bar you need to figure out what should happen if the user press a second button before the first one has finished:

    1. Cancel the current task.
    2. Save the last created progress object, and only update the progress bar if the event is raised from this object
    3. Use a queue of progress objects, only update the progress bar for the first progress in the queue.
    4. Use a list of progress bars, one for each task.