65.9K
CodeProject 正在变化。 阅读更多。
Home

ProgressForm:一个与 BackgroundWorker 链接的简单表单

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (34投票s)

2011年2月21日

CPOL

3分钟阅读

viewsIcon

104222

downloadIcon

7912

ProgressForm 在加载时自动启动一个 BackgroundWorker,并提供进度条和取消按钮。

引言

这是我在 CodeProject 上的第一篇文章,希望您觉得它有用。

当我开发软件时,我经常需要要求用户等待一个长时间的操作完成,并且通常允许他/她取消它。无论我在该操作中做什么(它可以是下载文件、将大文件保存到磁盘等),我总是需要相同的东西

  • 我希望用户通过模态对话框等待操作完成
  • 我希望用户能看到其进度
  • 我希望用户能够取消它

由于我没有在网上找到用于此目的的“即用型”表单(也许我没有搜索好?),我决定编写自己的表单。

ProgressForm.JPG

背景

BackgroundWorker 类包含我们需要完成此操作的所有内容。我们只需要在它周围提供一个对话框即可。

使用代码

ProgressForm 已经包含一个 BackgroundWorker,您只需要提供一个执行工作的 方法即可。

ProgressForm form = new ProgressForm();
form.DoWork += new ProgressForm.DoWorkEventHandler(form_DoWork);
//if you want to provide an argument to your background worker
form.Argument = something;

要启动 BackgroundWorker,只需调用 ShowDialog。返回值将取决于 worker 的完成方式

DialogResult result = form.ShowDialog();
if (result == DialogResult.Cancel)
{
     //the user clicked cancel
}
else if (result == DialogResult.Abort)
{
     //an unhandled exception occured in user function
     //you may get the exception information:
     MessageBox.Show(form.Result.Error.Message);
}
else if (result == DialogResult.OK)
{
     //the background worker finished normally
     //the result of the background worker is stored in form.Result
}

最后,worker 方法将如下所示

void form_DoWork(ProgressForm sender, DoWorkEventArgs e)
{
    //get the provided argument as usual
    object myArgument = e.Argument;
 
    //do something long...
    for (int i = 0; i < 100; i++)
    {
        //notify progress to the form
        sender.SetProgress(i, "Step " + i.ToString() + " / 100...");
        
        //...
        
        //check if the user clicked cancel
        if (sender.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
    }
}

如果您只想更改进度条或仅更改进度文本,SetProgress 有几个重载

public void SetProgress(string status);
public void SetProgress(int percent);
public void SetProgress(int percent, string status);

最后可自定义的内容:有两个预定义的字符串:CancellingTextDefaultStatusTextCancellingText 是用户单击“取消”时将显示的文本。

工作原理

ProgressForm 只是嵌入一个 BackgroundWorker 并包装其主要功能。

首先,我设计了一个如图所示的表单。然后我添加了带有主要事件处理程序的 BackgroundWorker

public partial class ProgressForm : Form
{
    public ProgressForm()
    {
         InitializeComponent();
 
         worker = new BackgroundWorker();
         worker.WorkerReportsProgress = true;
         worker.WorkerSupportsCancellation = true;
         worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
         worker.ProgressChanged += new ProgressChangedEventHandler(
             worker_ProgressChanged);
         worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
             worker_RunWorkerCompleted);
    }
    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
    }
    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    }
    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
    }
    BackgroundWorker worker;
}

现在我们必须向用户公开 DoWork 事件。我添加了一个新的委托,以便我们可以轻松访问表单成员

public delegate void DoWorkEventHandler(ProgressForm sender, DoWorkEventArgs e);
public event DoWorkEventHandler DoWork;

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    //the background worker started
    //let's call the user's event handler
    if (DoWork != null)
        DoWork(this, e);
}

好的,我们有了我们的 worker 和用户的事件。现在我们希望 worker 在显示表单后立即启动。让我们在 Load 事件中执行此操作

void ProgressForm_Load(object sender, EventArgs e)
{
    worker.RunWorkerAsync();
}

现在,让我们编写一个方法来通知进度,并在我们的 ProgressChanged 事件处理程序中添加代码

public void SetProgress(int percent, string status)
{
    worker.ReportProgress(percent, status);
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    if (e.ProgressPercentage >= progressBar.Minimum &&
        e.ProgressPercentage <= progressBar.Maximum)
    {
        progressBar.Value = e.ProgressPercentage;
    }
    if (e.UserState != null)
        labelStatus.Text = e.UserState.ToString();
}

我们快完成了。现在我们只需要处理“取消”按钮

void buttonCancel_Click(object sender, EventArgs e)
{
    //notify the worker we want to cancel
    worker.CancelAsync();
    //disable the cancel button and change the status text
    buttonCancel.Enabled = false;
    labelStatus.Text = "Cancelling..."
}

最后一件事:我们希望在 worker 完成后自动关闭表单,并且由于我们的 worker 将通过 ShowDialog 方法启动,如果它可以直接返回结果会很好

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //ShowDialog return value will inform whether the worker finished properly or not
    if (e.Error != null)
        DialogResult = DialogResult.Abort;
    else if (e.Cancelled)
        DialogResult = DialogResult.Cancel;
    else
        DialogResult = DialogResult.OK;
    //close the form
    Close();
}

主要工作完成了!我添加了一些其他内容,例如状态的预定义字符串、防止在取消挂起时更改状态,或者将参数传递给 worker 函数。

您将在此处找到该类的完整源代码

/// <summary>
/// Simple progress form.
/// </summary>
public partial class ProgressForm : Form
{
    /// <summary>
    /// Gets the progress bar so it is possible to customize it
    /// before displaying the form.
    /// Do not use it directly from the background worker function!
    /// </summary>
    public ProgressBar ProgressBar { get { return progressBar; } }
    /// <summary>
    /// Will be passed to the background worker.
    /// </summary>
    public object Argument { get; set; }
    /// <summary>
    /// Background worker's result.
    /// You may also check ShowDialog return value
    /// to know how the background worker finished.
    /// </summary>

    public RunWorkerCompletedEventArgs Result { get; private set; }
    /// <summary>
    /// True if the user clicked the Cancel button
    /// and the background worker is still running.
    /// </summary>
    public bool CancellationPending
    {
        get { return worker.CancellationPending; }
    }

    /// <summary>
    /// Text displayed once the Cancel button is clicked.
    /// </summary>
    public string CancellingText { get; set; }
    /// <summary>
    /// Default status text.
    /// </summary>
    public string DefaultStatusText { get; set; }
    /// <summary>
    /// Delegate for the DoWork event.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">Contains the event data.</param>
    public delegate void DoWorkEventHandler(ProgressForm sender, DoWorkEventArgs e);
    /// <summary>
    /// Occurs when the background worker starts.
    /// </summary>
    public event DoWorkEventHandler DoWork;

    /// <summary>
    /// Constructor.
    /// </summary>
    public ProgressForm()
    {
        InitializeComponent();

        DefaultStatusText = "Please wait...";
        CancellingText = "Cancelling operation...";

        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.WorkerSupportsCancellation = true;
        worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
        worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
        worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
            worker_RunWorkerCompleted);
    }

    /// <summary>
    /// Changes the status text only.
    /// </summary>
    /// <param name="status">New status text.</param>
    public void SetProgress(string status)
    {
        //do not update the text if it didn't change
        //or if a cancellation request is pending
        if (status != lastStatus && !worker.CancellationPending)
        {
            lastStatus = status;
            worker.ReportProgress(progressBar.Minimum - 1, status);
        }
    }
    /// <summary>
    /// Changes the progress bar value only.
    /// </summary>
    /// <param name="percent">New value for the progress bar.</param>
    public void SetProgress(int percent)
    {
        //do not update the progress bar if the value didn't change
        if (percent != lastPercent)
        {
            lastPercent = percent;
            worker.ReportProgress(percent);
        }
    }
    /// <summary>
    /// Changes both progress bar value and status text.
    /// </summary>
    /// <param name="percent">New value for the progress bar.</param>
    /// <param name="status">New status text.</param>
    public void SetProgress(int percent, string status)
    {
        //update the form is at least one of the values need to be updated
       if (percent != lastPercent || (status != lastStatus && !worker.CancellationPending))
       {
           lastPercent = percent;
           lastStatus = status;
           worker.ReportProgress(percent, status);
       }
    }

    private void ProgressForm_Load(object sender, EventArgs e)
    {
        //reset to defaults just in case the user wants to reuse the form
        Result = null;
        buttonCancel.Enabled = true;
        progressBar.Value = progressBar.Minimum;
        labelStatus.Text = DefaultStatusText;
        lastStatus = DefaultStatusText;
        lastPercent = progressBar.Minimum;
        //start the background worker as soon as the form is loaded
        worker.RunWorkerAsync(Argument);
   }

    private void buttonCancel_Click(object sender, EventArgs e)
    {
        //notify the background worker we want to cancel
        worker.CancelAsync();
        //disable the cancel button and change the status text
        buttonCancel.Enabled = false;
        labelStatus.Text = CancellingText;
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        //the background worker started
        //let's call the user's event handler
        if (DoWork != null)
            DoWork(this, e);
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        //make sure the new value is valid for the progress bar and update it
        if (e.ProgressPercentage >= progressBar.Minimum &&
            e.ProgressPercentage <= progressBar.Maximum)
        {
            progressBar.Value = e.ProgressPercentage;
        }
        //do not update the text if a cancellation request is pending
        if (e.UserState != null && !worker.CancellationPending)
            labelStatus.Text = e.UserState.ToString();
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        //the background worker completed
        //keep the resul and close the form
        Result = e;
        if (e.Error != null)
            DialogResult = DialogResult.Abort;
        else if (e.Cancelled)
            DialogResult = DialogResult.Cancel;
        else
            DialogResult = DialogResult.OK;
        Close();
    }

    BackgroundWorker worker;
    int lastPercent;
    string lastStatus;
}

结论

此表单非常简单,但由于我经常使用它,我认为它对你们中的一些人会很有用。

历史

  • 修订版 2:添加了“工作原理”部分。
  • 修订版 3:再次上传了 SampleApplication.zip
  • 修订版 4:添加了 ProgressBar 属性并更改了 SetProgress 函数,以便它们仅在需要时调用 ReportProgress
© . All rights reserved.