Evil 域

当Evil遇上先知

导航

利用IProgress轻松搞定进度条编程

Posted on 2011-07-05 09:09  Saar  阅读(6084)  评论(1编辑  收藏  举报

毫无疑问,当一个任务需要较长时间才能完成进,如果有一个进度条显示进度,会比简单的显示一个Please Wait要让人感觉好很多。
然而,一旦涉及进度条,那么,程序至少需要同时做两件事(好吧,也许叫异步的做两件事更确切一点):第一,完成任务本身;第二,计算进度并更新UI。在C# 5.0 Aysnc中提供的IProgress<T>类,为进度条编程提供了便捷的方法。三步即可完成一个简单的进度条。
下面的示例基于Async CTP制作。要能编译、运行示例,需要以下准备:

    1. Visual Studio 2010 SP1.
    2. Async CTP(SP1 Refresh)
    3. 一个WPF应用程序,并且有一个进度条,顺便给进度条取个名,例如progressBar之类的 :-)

如果你还不知道Async是什么,点这里
好了,这样,我们就可以开始示例了。
第一步,写一个类,用于存放参数,我们用这些参数来计算进度。一个最简单的例子就是有两个整数值,一个是当前完成的值,另一个是总值。所以,我们就有了这样一个类:

   1:          //Step 1: Have a class to store current value / total value.
   2:          public class ProgressPartialResult
   3:          {
   4:              public int Current { get; set; }
   5:              public int Total { get; set; }
   6:          }

有些时候,计算进度可能相当的复杂,但是,原则是一样的,把需要的参数全部做在这个类里就行了。当然,也有一种简单的极品:这个类就是一个System.Integer :-)那你就不用自己写了。

第二步,创建一个IProgress<T>的对像,其中,T就是我们刚才创建的类,并且添加相应的处理逻辑,用来处理当进度更新发生时要做的事情。示例中,一旦进度更新,我们就更新UI里的进度条。计算也很简单:当前值/总值*100。

   1:          private void DoProgress()
   2:          {
   3:              //Step 2: Create an object of IProgress<T> to trace the progress & add event handler when new progress reported.
   4:              var progress = new Progress<ProgressPartialResult>();
   5:              progress.ProgressChanged += new ProgressEventHandler<ProgressPartialResult>(progress_ProgressChanged);
   6:              
   7:              //Step 3: Call async logic with progress report.
   8:              DoSomething(progress);
   9:          }
  10:   
  11:          void progress_ProgressChanged(object sender, MainWindow.ProgressPartialResult value)
  12:          {
  13:              progressBar.Value = (float)value.Current / value.Total * 100;
  14:          }

好了,我想你已经知道第三步要做什么了:异步完成任务并且汇报进度。

   1:          private async void DoSomething(IProgress<ProgressPartialResult> progress)
   2:          {
   3:              int total = 100;
   4:              for (int i = 0; i <= total; i++)
   5:              {
   6:                  await TaskEx.Delay(20); //Do things for 0.02 seconds.
   7:                  //Report the progress
   8:                  if (progress != null)
   9:                  {
  10:                      progress.Report(new ProgressPartialResult() { Current = i + 1, Total = total });
  11:                  }
  12:              }
  13:              progress.Report(new ProgressPartialResult() { Current = 0, Total = total });
  14:          }

稍微看一下代码,我们假设总量是100,进度for循环以后,首先做任务本身,我们假设每次做0.02秒。然后,就汇报一下进度。当退出循环以后,把进度置为0。

整理一下,整个事情其实是这样的,进度条编程围绕IProgress<T>的对象展开。每当我们在异步的做事的时候,我们就调用Report方法汇报一下进度,进度的参数我们通过一个预先定义好的类来传递。Report()方法会触发ProgressChanged事件,于是,就会调用事件处理者(Handler)逻辑。处理者则根据传递进来的参数,计算出当前的进度,并且做程序员预期它做的事,例如,更新UI上的进度条之类的。

大家可以试一下进度条行进时拖动UI做点别的事玩玩^v^希望本文能够给你带来一些娱乐 :-)

贴个完整的Code Behind。UI部分大家就自行搞定吧。最最后面附简陋的应用程序贴图。

 

using System;
using System.Threading.Tasks;
using System.Windows;
 
namespace WpfApplication3
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
 
            this.DoProgress();
        }
 
        //Step 1: Have a class to store current value / total value.
        public class ProgressPartialResult
        {
            public int Current { get; set; }
            public int Total { get; set; }
        }
 
        private void DoProgress()
        {
            //Step 2: Create an object of IProgress<T> to trace the progress & add event handler when new progress reported.
            var progress = new Progress<ProgressPartialResult>();
            progress.ProgressChanged += new ProgressEventHandler<ProgressPartialResult>(progress_ProgressChanged);
            
            //Step 3: Call async logic with progress report.
            DoSomething(progress);
        }
 
        void progress_ProgressChanged(object sender, MainWindow.ProgressPartialResult value)
        {
            progressBar.Value = (float)value.Current / value.Total * 100;
        }
 
        private async void DoSomething(IProgress<ProgressPartialResult> progress)
        {
            int total = 100;
            for (int i = 0; i <= total; i++)
            {
                await TaskEx.Delay(20); //Do things for 2 seconds.
                //Report the progress
                if (progress != null)
                {
                    progress.Report(new ProgressPartialResult() { Current = i + 1, Total = total });
                }
            }
            progress.Report(new ProgressPartialResult() { Current = 0, Total = total });
        }
 
    }
}

ProgressBar

 

7/5:关于原文的一点点补充:

前文提到,进度条编程围绕IProress<T>展开。那么,为什么我们需要这个接口,换句话说,IProgress<T>给我们带来了什么?个人的理解如下,如果我们要异步的完成一个任务,例如DoSomething,并且我们已经有一系列函数可以帮且我们完成这个任务了,那么,调用这个函数时,我们可能会接触到以下一些函数的签名:

void DoSomething();

Task DoSomethingAsync();

Task DoSomethingAsync(CancellationToken ct);

Task DoSomethingAsync(CancellationToken ct, IProgress<int> progress);

再如果一下,如果我们想要知道DoSomething的进度,选哪一个会更合理一些?……好吧,每个人都有选择的权力^v^大家自己参?OK。

其实这四个函数就像四个员工,第一个员工闷头干活,什么都不管;第二个会找别人干活,他本身负责协调,适合同一时间多做几件事(好同志啊),但是,一件活他一旦开始做了,天塌下来他也会继续做;第三个员工,跟二个差不多,只是随叫随停;第四个不仅随叫随停,而且,经常汇报进度。

所以,回到IProgress<T>上来,因为有了它,我们调用有它的函数时,虽然不知道执行的细节,仍然可以轻易的知道事态的进度;在写函数时,如果想要提供一个“进度友好”的函数,不管是给自己还是给别人用,把IProgress<T>作为参数都是一个不错的选择。