在程式執行中, 有時後需要處理一些比較耗時的程序, 例如讀取或計算大量的資料, 因為這些動作會花比較多的時間, 而且會讓畫面停留不動. 所以, 通常需要一個Loading/Processing的畫面來提醒使用者.
在Loading過程中, 如果要更新UI內容的話, 需要在背景工作, 否則會等Loading處理完畢後, 才會更新UI.
下面的範例中, 程式會顯示Loading過程中的次數和完成的百分比.
private void UpdateProcess()
{
for (int i = 1; i <= _numberOfTasks; i++)
{
labelNumber.Content = i +"/" + _numberOfTasks;
float percentageDone = (i / (float)_numberOfTasks) * 100f;
labelPercent.Content = (int)percentageDone+"%";
Thread.Sleep(1);
}
}
從執行結果可看出, 因為不是在背景中(background)更新UI內容, 所以畫面會停住不動, 等Loading完才會更新UI內容
[影片1] 不在背景工作中更新UI
在WPF中提供多個背景工作的方法, 下面逐一介紹
此方法最為簡單, 我們可將Dispatcher Priority 設成 DispatcherPriority.Background,即可在背景執行,而Dispatcher的相關用法可參考這篇文章WPF - Thread and Dispatcher
因此我們可將上面的程式碼, 改成為下面的程式:
因此我們可將上面的程式碼, 改成為下面的程式:
private delegate void UpdateProcessDelegate(int index, int percent);
private void UpdateProcessAction(int index, int percent)
{
labelNumber.Content = index + "/" + _numberOfTasks;
labelPercent.Content = percent + "%";
}
private void UpdateProcess()
{
for (int i = 1; i <= _numberOfTasks; i++)
{
//labelNumber.Content = i +"/" + _numberOfTasks;
float percentageDone = (i / (float)_numberOfTasks) * 100f;
//labelPercent.Content = (int)percentageDone+"%";
Dispatcher.Invoke(DispatcherPriority.Background,
new UpdateProcessDelegate(UpdateProcessAction),
i,
(int)percentageDone);
Thread.Sleep(1);
}
}
[影片2] 使用Dispatcher更新UI內容
2. 使用DispatcherFrame
此作法類似Windows Form的DoEvents.
DispatcherFrame 表示處理暫停之工作項目的迴圈.發送器會處理迴圈中的工作項目佇列. 此迴圈即稱為框架。通常是由應用程式以呼叫 Run 的方式來初始化初始迴圈. PushFrame 進入以 frame 參數表示的迴圈。對該迴圈的每次查看, Dispatcher 都會檢查 DispatcherFrame 類別上的 Continue 屬性,以判斷迴圈是否應繼續或停止.
程式碼如下:
DispatcherFrame 表示處理暫停之工作項目的迴圈.發送器會處理迴圈中的工作項目佇列. 此迴圈即稱為框架。通常是由應用程式以呼叫 Run 的方式來初始化初始迴圈. PushFrame 進入以 frame 參數表示的迴圈。對該迴圈的每次查看, Dispatcher 都會檢查 DispatcherFrame 類別上的 Continue 屬性,以判斷迴圈是否應繼續或停止.
程式碼如下:
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame),
frame);
Dispatcher.PushFrame(frame);
}
private object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
private void UpdateProcess()
{
for (int i = 1; i <= _numberOfTasks; i++)
{
labelNumber.Content = i +"/" + _numberOfTasks;
float percentageDone = (i / (float)_numberOfTasks) * 100f;
labelPercent.Content = (int)percentageDone+"%";
DoEvents();
Thread.Sleep(1);
}
}
3. 使用BackgroundWorker
BackgroundWorker可以在個別的執行緒上執行. BackgroundWoker常用的function如下:
- DoWork -設定背景工作的地方, 會將需要耗時的作業放在此事件處理函式
- ProgressChanged - 收進度更新的狀態, UI可在此事件處理函式中作更新
- RunWorkerAsync - 啟動執行緒
- RunWorkerCompleted - 作業完成時會觸發此事件
private void btnShowText_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(DuringWork);
// start the background work
worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= _numberOfTasks; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(1);
float percentageDone = (i / (float)_numberOfTasks) * 100f;
worker.ReportProgress((int)percentageDone, i);
}
}
void DuringWork(object sender, ProgressChangedEventArgs e)
{
labelNumber.Content = e.UserState + "/" + _numberOfTasks;
labelPercent.Content = e.ProgressPercentage.ToString() + "%";
}
參考文章:
沒有留言:
張貼留言