打開電腦,突然想起來,昨天下午,有位仁兄和我討論過一個事,不妨拿來說說。
她說她的 牛逼程序要處理一堆東東,要弄個進度條作提示,不過進度條是在另一個窗口中的,她的想法是,在開 始處理數據時彈出進度對話框,實時顯示處理進度,當處理完成後關閉對話框。乍看起來其實不難,不 過她遇到了以下問題,故在群裡提問。
1、模態對話框的問題。
這問題好辦,一般來說, 要長時間來處理數據,應該考慮後台異步操作,用磚家的話講就是多線程。不過她在顯示窗口時調用了 ShowDialog方法,這樣代碼會一直停在那裡,直到窗口關閉。
如果是異步操作,通常來說,在啟 動後台任務後會馬上返回,這麼一來,只要把代碼的順序調一下就可以解決這問題,先啟動後台任務, 再調用ShowDialog方法,這樣一來,就算代碼停在ShowDialog那裡也不會影響後台任務的執行。
2、如何控制其他窗口中控件。
可以在在窗口類中定義公共方法來對控件進行某些操作, 之後在其他地方就可以通過這些公共方法來調控。如果是跨線程調用,應當考慮使用委托或事件來調用 。不然你學了委托和事件干嗎呢?
另一種方法就是直接把進度窗口中的ProgressBar控件聲明為 public,這樣其他類就可以輕松訪問了。
3、後台任務如何更簡單。
方法是靈活的,有很 多種。最簡單的是利用.NET 4.5和C# 5.0 中的新特性,這種方法肯定是最簡單的。第二種則是使用.NET 4 中新加的Task類來調度線程;比較傳統,在2.0時代用得最多的方法是直接用Thread類。
但是 ,有一個組件是專為後台任務而開放的,忘了沒有?——BackgroundWorker,也許有些朋友是忘了這個 組件了。現在很多人就是這樣,有了小三就忘了結發妻子,我們眾多碼農也是這樣,常常會忘本。
既有後台任務,又要報告進度,用BackgroundWorker不是更合適嗎?於是我問了她兩遍:“還記 得嗎?” 果然不出我所料,記不起來了,呵呵。
說了那麼多,估計需要實例才能解決問題,好 吧,上菜。
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屬性。
現在,我們可以看看最終的效果,是否達到預期 要求。
查看本欄目