using backgroundworker in Winforms (C#) with MVP pattern

自闭症网瘾萝莉.ら 提交于 2019-12-05 03:16:13

问题


I've been trying to refactor a spaghetti code of an app by using MVP pattern. But now I'm struggling with this:

A form that has button that calls a the DoWork method (of a backgroundworker) which is a long operation. My question is if I move the long operation out of the view into the Presenter then how do I send progress changes from this operation to the View? The BGW must be in the Presenter also? Can you give me a sample of how to do this?

Thank you in advance.


回答1:


This outlines the use of the BackgroundWorker:

private BackgroundWorker _backgroundWorker;

public void Setup( )
{
    _backgroundWorker = new BackgroundWorker();
    _backgroundWorker.WorkerReportsProgress = true;
    _backgroundWorker.DoWork +=
      new DoWorkEventHandler(BackgroundWorker_DoWork);
    _backgroundWorker.ProgressChanged +=
      new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
    _backgroundWorker.RunWorkerCompleted +=
      new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);

    // Start the BackgroundWorker
    _backgroundWorker.RunWorkerAsync();
}

void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // This method runs in a background thread. Do not access the UI here!
    while (work not done) {
        // Do your background work here!

        // Send messages to the UI:
        _backgroundWorker.ReportProgress(percentage_done, user_state);
        // You don't need to calculate the percentage number if you don't
        // need it in BackgroundWorker_ProgressChanged.
    }
    // You can set e.Result = to some result;
}

void BackgroundWorker_ProgressChanged(object sender,
                                      ProgressChangedEventArgs e)
{
    // This method runs in the UI thread and receives messages from the backgroud thread.

    // Report progress using the value e.ProgressPercentage and e.UserState
}

void BackgroundWorker_RunWorkerCompleted(object sender,
                                         RunWorkerCompletedEventArgs e)
{
    // This method runs in the UI thread.
    // Work is finished! You can display the work done by using e.Result
}

UPDATE

This BackgroundWorker has to be in the presenter of cause. The idea of patterns like MVP, MVC or MVVM is to remove as much code from the view as possible. The view would only have code very specific to the view itself, like creating the view or drawing in the Paint event handler and so on. Another kind of code in the view is the code necessary to communicate with the presenter or controller. The presenting logic, however, has to be in the presenter.

You would use the BackgroundWorker_ProgressChanged method that runs in the UI thread to send changes to the view. Either by calling public methods of the view or by setting public properties of the view or by exposing public properties the view can attach to by binding its properties or the properties of its controls to it. (This is borrowed from the MVVM pattern.) The presenter must implement INotifyPropertyChanged in order to notify the view that a property has changed, if you decide to bind the view to properties of the presenter.

Note: Another thread than the UI thread is not allowed to interact with the view directly (an exception is thrown if you try to do so). Therefore the BackgroundWorker_DoWork cannot interact with the view directly and therefore calls ReportProgress, which in turn runs BackgroundWorker_ProgressChanged in the UI thread.




回答2:


You can place the BackGroundWorker in the presenter and add a method to the view to show the progress. Something like this:

//Add a method to your view interface to show progress if you need it.
public interface IView
{
     void ShowProgress(int progressPercentage);
}
//Implement method in the view.
public class MyView : Form, IView
{
    public MyView()
    {
        //Assume you have added a ProgressBar to the form in designer.
        InitializeComponent();
    }

    public void ShowProgress(int progressPercentage)
    {
        //Make it thread safe.

        if (progressBar1.InvokeRequired)
            progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
        else
            progressBar1.Value = progressPercentage;
    }
}

// In your presenter class create a BackgroundWorker and handle it's do work event and put your time consuming method there.
public class MyPresenter
{
    private BackgroundWorker _bw;

    public MyPresenter()
    {
        _bw = new BackgroundWorker();
        _bw.WorkerReportsProgress = true;
        _bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
    }

    private void _bw_DoWork(object sender, DoWorkEventArgs e)
    {
        //Time consuming operation
        while (!finished)
        {
            //Do the job
            _bw.ReportProgress(jobProgressPercentage);

        }
    }

    public void StartTimeConsumingJob()
    {
        _bw.RunWorkerAsync();
    }
}

Don't forget to Dispose the BackgroundWorker when you're finished.




回答3:


with your input I've managed to work this out. Please comment any flaws you may find with this approach:

* View interface *

public interface IView
{
   void ShowProgress( int progressPercentage);
}

* View (a form) *

public partial class Form1 : Form, IView
    {
        MyPresenter p ;

        public Form1()
        {
            InitializeComponent();
            p = new MyPresenter(this);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (p.IsBusy())
            {
                return;
            }
            p.StartTimeConsumingJob();
        }

        public void ShowProgress(int progressPercentage)
        {
            if (progressBar1.InvokeRequired)
                progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
            else
                progressBar1.Value = progressPercentage;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            p.Cancel();
        }

    }

* Presenter *

public class MyPresenter
{
    private BackgroundWorker _bw;
    private IView _view;

    public MyPresenter(IView Iview)
    {
        _view = Iview;
        _bw = new BackgroundWorker();
        _bw.WorkerReportsProgress = true;
        _bw.WorkerSupportsCancellation = true;
        _bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
        _bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
        _bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_Completed);
    }

    public void StartTimeConsumingJob()
    {
        _bw.RunWorkerAsync();
    }

    private void _bw_DoWork(object sender, DoWorkEventArgs e)
    {
        //Time consuming operation Do the job
        Thread.Sleep(1000);
        _bw.ReportProgress(50);
        Thread.Sleep(2000);
        if(_bw.CancellationPending)
        {
            e.Result = false;
        }
    }

    public bool IsBusy()
    {
        return _bw.IsBusy;
    }

    public void Cancel()
    {
        _bw.CancelAsync();
    }

    private void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        _view.ShowProgress(e.ProgressPercentage);
    }

    private void _bw_Completed(object sender, RunWorkerCompletedEventArgs e)
    {
        if((bool)e.Result)
        _view.ShowProgress(100);
        else
            _view.ShowProgress(0);

        _bw.Dispose();
    }
}


来源:https://stackoverflow.com/questions/12765047/using-backgroundworker-in-winforms-c-with-mvp-pattern

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!