Search code examples
c#winformsmvp

mvp async progress winforms


i implemented a simple ProgressPresenter

public interface IProgressView
{
    string Status { set; }
    void SetProgress(int percentageDone);

    void Display();
    void Close();

    event Action Closing;
}

class ProgressPresenter
{
    private IProgressView m_view;
    private ILongRunningTask m_task;
    private bool m_completed;

    public Progress(IProgressView view)
    {
        m_view = view;
    }

    public virtual void Display(ILongRunningTask task, string taskName)
    {
        m_task = task;

        m_view.Status = taskName " is running";

        m_view.Closing += OnClosing;
        task.ProgressChanged += UpdateProgress;
        task.Completed += Completed;

        task.StartAsync();

        m_view.Display();

        m_view.Closing -= OnClosing;
        task.ProgressChanged -= UpdateProgress;
        task.Completed -= Completed;
    }

    protected virtual void UpdateProgress(object sender, ProgessEventArgs e)
    {
        m_view.SetProgress(e.AlreadyDone * 100 / e.Total);
    }

    protected virtual void Completed()
    {
        m_completed = true;
        m_view.Status = "Completed";
        m_view.Close();
    }

    private virtual void OnClosing()
    {
        if (!m_completed) m_downloader.Cancel();
    }
}

My problem is that the task is running in another thread and each call to the view (implemented as a Form) throws. Should i wrap each method in the Form like

public string Status
{
    set { Invoke(new Action(() => progressLabel.Text = value)); }
}

just in case it could be called from another thread? or is the Presenter flawed?

Any advice is appreciated


Solution

  • Yes, you should do that. I don't know what other libraries are you using, but is probably a good idea to add an aspect to all your views to do that for you.

    Also it might be worth adding a couple of friendly methods in a base view; E.g. I've got these ones:

        public void Invoke(Action action)
        {
            if (_control.InvokeRequired)
            {
                _control.Invoke(action);
                return;
            }
    
            action();
        }
    
        public T Invoke<T>(Func<T> action)
        {
            if (_control.InvokeRequired)
                return (T)_control.Invoke(action);
    
            return action();
        }
    

    for an example of the aspect implementation check here