在.NET WinForm程式中,大多數情況下我們是知道程式運行所需要的時間或步驟的,比如批量複製檔案時檔案的數量,資料匯出或匯入時資料的總行數等等。對於步驟比較確定的操作,如果程式執行過程時間較長,很容易使用BackgroundWorker結合ProgressBar來顯示一個即時的進度。相關內容大家可以看我部落格中的其它文章,有關如何使用BackgroundWorker和ProgressBar。但是,有的時候我們是不確定程式執行的具體步驟或時間長度的,比如串連一個遠程服務或資料庫服務,或者調用一個遠程過程或WebService等,這個時候我們就沒有辦法去觸發BackgroundWorker的ProgressChanged事件,因此也就不能即時去更新ProgressBar的進度了。有兩種替代的辦法可以解決這個問題。
第一是將ProgressBar的Style設定為Marquee而不是預設的Blocks。在Marquee模式下,進度條會不停地向前走用來類比一個長時間的操作。事實上,Windows中也有很多類似的進度條,大多都是出現在對操作過程所需的步驟和時間長度不太確定的時候。這種方法很簡單,不過你仍然要將背景執行過程放到多線程來執行,否則進度條會卡在UI線程中。一個好的辦法就是依舊使用BackgroundWorker組建,將背景執行程式放到BackgroundWorker的DoWorker事件中,然後調用BackgroundWorker的RunWorkerAsync方法來非同步執行程式。這樣,UI線程和後台執行程式的線程可以分開,進度條便不會再卡了。
第二種方法是使用System.Windows.Forms.Timer定時器控制項,設定好Timer的Interval間隔時間,在Timer的Tick事件中來更新ProgressBar的進度。由於Timer天生就是多線程的,所以這種辦法實現起來很方便。
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.Windows.Forms;
9 using System.Threading;
10
11 namespace WindowsFormsApplication2
12 {
13 public partial class Form1 : Form
14 {
15 private BackgroundWorker worker = new BackgroundWorker();
16 private System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
17 public Form1()
18 {
19 InitializeComponent();
20 this.progressBar1.Value = 0;
21 this.progressBar1.Maximum = 200;
22 this.progressBar1.Step = 1;
23 timer.Interval = 100;
24 timer.Tick += new EventHandler(timer_Tick);
25 worker.WorkerReportsProgress = true;
26 worker.DoWork += new DoWorkEventHandler(worker_DoWork);
27 worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
28 worker.RunWorkerAsync();
29 timer.Start();
30 }
31
32 void timer_Tick(object sender, EventArgs e)
33 {
34 if (this.progressBar1.Value < this.progressBar1.Maximum)
35 {
36 this.progressBar1.PerformStep();
37 }
38 }
39
40 void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
41 {
42 timer.Stop();
43 this.progressBar1.Value = this.progressBar1.Maximum;
44 MessageBox.Show("Complete!");
45 }
46
47 void worker_DoWork(object sender, DoWorkEventArgs e)
48 {
49 int count = 100;
50 for (int i = 0; i < count; i++)
51 {
52 Thread.Sleep(100);
53 }
54 }
55 }
56 }
Timer每隔100毫秒便調用一次Tick事件,在該事件中更新ProgressBar的當前進度。注意需要判斷ProgressBar的Value必須小於Maximum值時才去執行Performance()方法,否則會出現ProgressBar的Value大於Maximum的值而拋異常。根據BackgroundWorker的DoWork方法執行所需的時間長短不同,ProgressBar的進度可能會在BackgroundWorker執行具體操作完成之前到達100%,也可以沒有到達100%,所以在BackgroundWorker的RunWorkerCompleted事件中將ProgressBar的進度更新為100%,以確保進度在最後是一個完成的狀態。
如果你在應用程式中確實需要使用進度條來提示使用者背景程式在完成一個耗時較長的操作,而且你還希望進度條能盡量類比出程式執行的步驟,而不是一個Marquee狀態,可以考慮使用Timer定時器控制項。因為,有的時候我們確實很難評估一個執行過程到底需要多少步驟或者需要多長時間才能完成。