在Winforms (C#)中使用BackgroundWorker和MVP模式

4
我一直在尝试使用MVP模式重构一个混乱的应用程序。但现在我正在为以下问题而苦恼:
一个表单有一个按钮,该按钮调用DoWork方法(一个后台工作器)进行长时间操作。我的问题是,如果我将长时间操作从视图移动到Presenter中,那么如何将此操作的进度更改发送到视图? BGW也必须在Presenter中吗?
您能否给我一个如何做到这一点的示例?
提前致谢。
3个回答

4

这里介绍了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
}

更新

这个BackgroundWorker必须在Presenter中。像MVP、MVC或MVVM这样的模式的思想是尽可能地从视图中删除代码。视图只会有特定于视图本身的代码,比如创建视图或在Paint事件处理程序中绘制等。视图中的另一种代码是与Presenter或Controller通信所必需的代码。然而,展示逻辑必须在Presenter中。

要向视图发送更改,您需要使用在UI线程中运行的BackgroundWorker_ProgressChanged方法。可以通过调用视图的公共方法、设置视图的公共属性或暴露视图可以连接到的公共属性来实现此操作,方法是将其属性或控件的属性绑定到它上面(这是从MVVM模式中借鉴来的)。如果决定将视图绑定到Presenter的属性,则Presenter必须实现INotifyPropertyChanged以通知视图属性已更改。

注意:不允许其他线程直接与视图交互(如果尝试这样做,会引发异常)。因此,BackgroundWorker_DoWork无法直接与视图交互,因此会调用ReportProgress,在UI线程中运行BackgroundWorker_ProgressChanged。


2
您可以将BackGroundWorker放置在Presenter中,并添加一个方法到View中以显示进度。类似这样:
//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();
    }
}

在完成任务后,不要忘记释放BackgroundWorker。


谢谢你的回答。但我仍然想知道展示进度如何与表单进行关联。根据你的例子,progressbar1 在 presenter 中吗? - Libas
progressbar1在视图中,可以是一个表单。我编辑了示例以使其更清晰。 - VahidNaderi
Presenter MyPresenter 应该包含一个对 viewerIView 引用,否则它将无法与 viewer 进行通信。请使用接口而不是 MyView,以便于其他 viewer 实现可以在不更改 presenter 的情况下使用。请单击此处查看 MVP 模式的具体实现:https://msdn.microsoft.com/en-us/library/ff649571.aspx - smwikipedia
...并且视图应该持有一个具体或接口化的Presenter实例。 - smwikipedia

1

有了您的输入,我已经成功解决了这个问题。请评论您可能发现的此方法的任何缺陷:

* 查看界面 *

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

* 查看(表单)*

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();
        }

    }

* 主持人 *

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();
    }
}

1
@Libas,唯一的问题是你的 View 不应该有对 Presenter 的引用。一个合适的 MVP 框架会为你完成绑定。虽然它对你来说是有效的,但从技术上讲,你已经违反了 MVP 模式,因为 View 不应该知道 Presenter 的存在,而 Presenter 知道 View 的存在。箭头在所有 MVP 图表中都是从 Presenter 指向 View,而不是相反的方向。 - onefootswill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接