今天一早醒来,猛然发现,很久没写博客了,算一算,其实应该不到一个月,可总觉得寰宇一个月,世上已千年。是啊,于是内心萌发了一股冲动的力量,今天该写点什么,可是,一时想不出来。

打开电脑,突然想起来,昨天下午,有位仁兄和我讨论过一个事,不妨拿来说说。

她说她的牛逼程序要处理一堆东东,要弄个进度条作提示,不过进度条是在另一个窗口中的,她的想法是,在开始处理数据时弹出进度对话框,实时显示处理进度,当处理完成后关闭对话框。乍看起来其实不难,不过她遇到了以下问题,故在群里提问。

1、模态对话框的问题。

这问题好办,一般来说,要长时间来处理数据,应该考虑后台异步操作,用砖家的话讲就是多线程。不过她在显示窗口时调用了ShowDialog方法,这样代码会一直停在那里,直到窗口关闭。

如果是异步操作,通常来说,在启动后台任务后会马上返回,这么一来,只要把代码的顺序调一下就可以解决这问题,先启动后台任务,再调用ShowDialog方法,这样一来,就算代码停在ShowDialog那里也不会影响后台任务的执行。

2、如何控制其他窗口中控件。

可以在在窗口类中定义公共方法来对控件进行某些操作,之后在其他地方就可以通过这些公共方法来调控。如果是跨线程调用,应当考虑使用委托或事件来调用。不然你学了委托和事件干吗呢?

另一种方法就是直接把进度窗口中的ProgressBar控件声明为public,这样其他类就可以轻松访问了。

3、后台任务如何更简单。

方法是灵活的,有很多种。最简单的是利用.NET 4.5和C# 5.0 中的新特性,这种方法肯定是最简单的。第二种则是使用.NET 4 中新加的Task类来调度线程;比较传统,在2.0时代用得最多的方法是直接用Thread类。

但是,有一个组件是专为后台任务而开放的,忘了没有?——BackgroundWorker,也许有些朋友是忘了这个组件了。现在很多人就是这样,有了小三就忘了结发妻子,我们众多码农也是这样,常常会忘本。

既有后台任务,又要报告进度,用BackgroundWorker不是更合适吗?于是我问了她两遍:“还记得吗?” 果然不出我所料,记不起来了,呵呵。

 

说了那么多,估计需要实例才能解决问题,好吧,上菜。

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Threading.Tasks;  
  9. using System.Windows.Forms;  
  10. using System.IO;  
  11.   
  12. namespace MyApp  
  13. {  
  14.     public partial class Form1 : Form  
  15.     {  
  16.         static string SaveDir = string.Empty;  
  17.         private FormProgress fpro = null;  
  18.         public Form1()  
  19.         {  
  20.             InitializeComponent();  
  21.             fpro = new FormProgress();  
  22.         }  
  23.   
  24.   
  25.         private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)  
  26.         {  
  27.             int fileCount = Convert.ToInt32(e.Argument);  
  28.             Random rand = new Random();  
  29.             byte[] buffer = new byte[2048];  
  30.             for (int i = 0; i < fileCount; i++)  
  31.             {  
  32.                 try  
  33.                 {  
  34.                     string fileName = Path.Combine(SaveDir, i.ToString() + ".tmp");  
  35.                     using (var stream = File.Create(fileName))  
  36.                     {  
  37.                         int n = 0;  
  38.                         int maxByte = 8 * 1024 * 1024;  
  39.                         while (n < maxByte)  
  40.                         {  
  41.                             rand.NextBytes(buffer);  
  42.                             stream.Write(buffer, 0, buffer.Length);  
  43.                             n += buffer.Length;  
  44.                         }  
  45.                     }  
  46.                 }  
  47.                 catch  
  48.                 {  
  49.                     continue;  
  50.                 }  
  51.                 finally  
  52.                 {  
  53.                     // 报告进度   
  54.                     this.backgroundWorker1.ReportProgress(i + 1);  
  55.                 }  
  56.             }  
  57.         }  
  58.   
  59.         private void button1_Click(object sender, EventArgs e)  
  60.         {  
  61.             if (Directory.Exists(SaveDir) == false)  
  62.             {  
  63.                 return;  
  64.             }  
  65.             button1.Enabled = false;  
  66.             int cout = Convert.ToInt32(this.nmbud.Value);  
  67.             this.fpro.progressBar1.Minimum = 0;  
  68.             this.fpro.progressBar1.Maximum = cout;  
  69.             this.fpro.progressBar1.Value = this.fpro.progressBar1.Minimum;  
  70.             this.backgroundWorker1.RunWorkerAsync(cout);  
  71.             // 在开始异步操作后ShowDialog   
  72.             // 这样即使代码停在那里也不会影响后台任务的执行   
  73.             fpro.ShowDialog(this);  
  74.         }  
  75.   
  76.         private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)  
  77.         {  
  78.             int val = e.ProgressPercentage;  
  79.             this.fpro.lblText.Text = string.Format("已生成{0}个文件。", val);  
  80.             this.fpro.progressBar1.Value = val;  
  81.         }  
  82.   
  83.         private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)  
  84.         {  
  85.             button1.Enabled = true;  
  86.             fpro.Hide();  
  87.             MessageBox.Show("操作完成。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);  
  88.         }  
  89.   
  90.         private void btnBrowsFd_Click(object sender, EventArgs e)  
  91.         {  
  92.             FolderBrowserDialog fd = new FolderBrowserDialog();  
  93.             fd.RootFolder = Environment.SpecialFolder.Desktop;  
  94.             if (DialogResult.OK == fd.ShowDialog())  
  95.             {  
  96.                 SaveDir = fd.SelectedPath;  
  97.             }  
  98.             fd.Dispose();  
  99.         }  
  100.     }  
  101. }  
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;

namespace MyApp
{
    public partial class Form1 : Form
    {
        static string SaveDir = string.Empty;
        private FormProgress fpro = null;
        public Form1()
        {
            InitializeComponent();
            fpro = new FormProgress();
        }


        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            int fileCount = Convert.ToInt32(e.Argument);
            Random rand = new Random();
            byte[] buffer = new byte[2048];
            for (int i = 0; i < fileCount; i++)
            {
                try
                {
                    string fileName = Path.Combine(SaveDir, i.ToString() + ".tmp");
                    using (var stream = File.Create(fileName))
                    {
                        int n = 0;
                        int maxByte = 8 * 1024 * 1024;
                        while (n < maxByte)
                        {
                            rand.NextBytes(buffer);
                            stream.Write(buffer, 0, buffer.Length);
                            n += buffer.Length;
                        }
                    }
                }
                catch
                {
                    continue;
                }
                finally
                {
                    // 报告进度
                    this.backgroundWorker1.ReportProgress(i + 1);
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (Directory.Exists(SaveDir) == false)
            {
                return;
            }
            button1.Enabled = false;
            int cout = Convert.ToInt32(this.nmbud.Value);
            this.fpro.progressBar1.Minimum = 0;
            this.fpro.progressBar1.Maximum = cout;
            this.fpro.progressBar1.Value = this.fpro.progressBar1.Minimum;
            this.backgroundWorker1.RunWorkerAsync(cout);
            // 在开始异步操作后ShowDialog
            // 这样即使代码停在那里也不会影响后台任务的执行
            fpro.ShowDialog(this);
        }

        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            int val = e.ProgressPercentage;
            this.fpro.lblText.Text = string.Format("已生成{0}个文件。", val);
            this.fpro.progressBar1.Value = val;
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            button1.Enabled = true;
            fpro.Hide();
            MessageBox.Show("操作完成。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void btnBrowsFd_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fd = new FolderBrowserDialog();
            fd.RootFolder = Environment.SpecialFolder.Desktop;
            if (DialogResult.OK == fd.ShowDialog())
            {
                SaveDir = fd.SelectedPath;
            }
            fd.Dispose();
        }
    }
}


FormProgress是放置了ProgressBar的另一个窗体,backgroundWorker1由设计器生成,在设计窗口,可以直接从工具箱中把BackgroundWorker拖到设计器窗口上。

代码没有难度,相信小姑娘能看懂的。下面我们总结一下BackgroundWorker的用法,相信很多书上都有介绍,哪怕是抄MSDN的书。

1、如果希望报告进度,WorkerReportsProgress属性必须为true,否则报告进度时会异常。如果允许通过调用CancelAsync方法取消任务,WorkerSupportsCancellation属性要为true。

2、处理DoWork事件,后台要处理的任务代码就写在该事件的处理方法中。

3、ProgressChanged事件,当调用ReportProgress方法报告进度后,会引发该事件,处理该事件实时更新进度条的显示。

4、当任务执行完成后会引发RunWorkerCompleted事件,如果后台任务需要返回结果,可从事件参数RunWorkerCompletedEventArgs.Result属性中取得结果。那么,这个结果是怎么设置的呢?不妨再看看前面的DoWork事件,它的事件参数DoWorkEventArgs的Result属性,当我们的任务执行完成时,把结果赋给该属性,随后引发RunWorkerCompleted事件时,会把结果传递到RunWorkerCompletedEventArgs.Result属性。

 

现在,我们可以看看最终的效果,是否达到预期要求。