如何在progressBar中显示每个文件下载进度?

时间:2013-10-28 22:24:09

标签: c# winforms

在我做了所有更改后,这是新的类代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net;
using unfreez_wrapper;
using System.Drawing;
using System.Globalization;

namespace WeatherMaps
{


    class ExtractImages
    {
        int count = 0;
        int length;
        string stringForSatelliteMapUrls = "http://www.sat24.com/";
        static int counter;
        UnFreezWrapper uf;
        List<string> imagesSatelliteUrls;
        List<string> imagesRainUrls;
        string localdir;

        // Instance with one List and Files and Animation
        public ExtractImages(List<string> mapToRead, string LocalFileDir, string UrlsDir)
        {
            counter = 0;
        }

        // Instance with more then one List and Files and Animation
        public ExtractImages(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
    {
        localdir = LocalFileDir;
        counter = 0;
        imagesSatelliteUrls = new List<string>();
        imagesRainUrls = new List<string>();
        int startIndex = 0;
        int endIndex = 0;
        int position = 0;
        for (int i = 0; i < Maps.Count; i++)
        {
            imagesSatelliteUrls.Add("Group " + (i + 1));
            string startTag = FirstTags[i];
            string endTag = LastTags[i];
            startIndex = Maps[i].IndexOf(startTag);
            while (startIndex > 0)
            {

                endIndex = Maps[i].IndexOf(endTag, startIndex);
                if (endIndex == -1)
                {
                    break;
                }
                string t = Maps[i].Substring(startIndex, endIndex - startIndex + endTag.Length);
                imagesSatelliteUrls.Add(t);
                position = endIndex + endTag.Length;
                startIndex = Maps[i].IndexOf(startTag, position);

            }
                string imageSatelliteUrl = imagesSatelliteUrls[i];
                if (!imagesSatelliteUrls[i].StartsWith("Group"))
                {
                    if (!imagesSatelliteUrls[i].StartsWith("http://"))
                    {
                        imagesSatelliteUrls[i] = "http://" + imagesSatelliteUrls[i];
                        imageSatelliteUrl = imagesSatelliteUrls[i];
                    }
                    if (!imagesSatelliteUrls[i].Contains("href"))
                    {
                        downloadQueue.Enqueue(
                            new DownloadData(
                                new Uri(imageSatelliteUrl),
                                UrlsDir + "SatelliteImage" + i.ToString("D6")
                            )
                        );
                    }
            }
        }
    }

        public class DownloadData
        {
            public Uri DownloadUri;
            public string TargetPath;

            public DownloadData(Uri downloadUri, string targetPath)
            {
                this.DownloadUri = downloadUri;
                this.TargetPath = targetPath;
            }
        }

因为在变量List中我有一些索引字符串“Group”我必须添加这个过滤器。 imageSatelliteUrl List中的链接也不是以http://开头的,所以我也添加了这个过滤器,但我不确定它是否有好办法。

在Form1中,我做了:

在Form1的顶部,我做了:

private WebClient _webClient = null;
private readonly Queue<ExtractImages.DownloadData> _downloadQueue = new Queue<ExtractImages.DownloadData>();
ExtractImages ei;

在Form1的构造函数中,我做了:

InitializeWebClient();

然后在Form1中的backgroundworker的DoWork事件中,我做了:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            lock (_downloadQueue)
            {
                ei = new ExtractImages(_downloadQueue, StartTags, LastTags, Maps, localFilename, UrlsPath);

                if (_downloadQueue.Count > 0)
                    foreach (ProgressBar pb in progressbars)
                    {
                        if (pb.Tag == null)
                        {
                            ExtractImages.DownloadData dd = _downloadQueue.Dequeue();
                            pb.Tag = dd;
                            _webClient.DownloadFileAsync(
                                dd.DownloadUri,
                                dd.TargetPath,
                                pb
                            );

                            if (_downloadQueue.Count == 0) break;
                        }
                    }
            }
        }

然后在Form1中我添加了这个方法:

private void InitializeWebClient()
        {
            _webClient = new WebClient();
            _webClient.DownloadFileCompleted += DownloadCompletedCallback;
            _webClient.DownloadProgressChanged += DownloadProgressCallback;
        }

最后添加了这两个事件:

private void DownloadCompletedCallback(object sender, AsyncCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                //... download cancelled...
            }
            else if (e.Error != null)
            {
                //... download failed...
            }

            ProgressBar pb = e.UserState as ProgressBar;

            lock (_downloadQueue)
            {
                if (_downloadQueue.Count == 0)
                {
                    if (pb != null) pb.Tag = null;
                }
                else
                {
                    ExtractImages.DownloadData dd = _downloadQueue.Dequeue();
                    if (pb != null) pb.Tag = dd;
                    _webClient.DownloadFileAsync(
                        dd.DownloadUri,
                        dd.TargetPath,
                        e.UserState
                    );
                }
            }
        }

        private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e)
        {
            ProgressBar pb = e.UserState as ProgressBar;
            if (pb != null) pb.Value = e.ProgressPercentage;
        }

但它的作用是下载一个文件我使用断点然后停止或不继续下载,我在progressBars中看不到任何内容。

编辑**

方法ExtractImages现在是:

public ExtractImages(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
        {
            localdir = LocalFileDir;
            counter = 0;
            imagesSatelliteUrls = new List<string>();
            imagesRainUrls = new List<string>();
            int startIndex = 0;
            int endIndex = 0;
            int position = 0;
            for (int i = 0; i < Maps.Count; i++)
            {
                imagesSatelliteUrls.Add("Group " + (i + 1));
                counter++;
                string startTag = FirstTags[i];
                string endTag = LastTags[i];
                startIndex = Maps[i].IndexOf(startTag);
                while (startIndex > 0)
                {

                    endIndex = Maps[i].IndexOf(endTag, startIndex);
                    if (endIndex == -1)
                    {
                        break;
                    }
                    string t = Maps[i].Substring(startIndex, endIndex - startIndex + endTag.Length);
                    imagesSatelliteUrls.Add(t);
                    position = endIndex + endTag.Length;
                    startIndex = Maps[i].IndexOf(startTag, position);

                }
                    string imageSatelliteUrl = imagesSatelliteUrls[i];
                    if (!imagesSatelliteUrls[i].StartsWith("Group"))
                    {
                        if (!imagesSatelliteUrls[i].StartsWith("http://"))
                        {
                            imagesSatelliteUrls[i] = "http://" + imagesSatelliteUrls[i];
                            imageSatelliteUrl = imagesSatelliteUrls[i];
                        }
                        if (!imagesSatelliteUrls[i].Contains("href"))
                        {
                            downloadQueue.Enqueue(
                                new DownloadData(
                                    new Uri(imageSatelliteUrl),
                                    UrlsDir + "SatelliteImage" + counter.ToString("D6")
                                )
                            );
                        }
                }
            }
        }

1 个答案:

答案 0 :(得分:2)

注意:为了使我的答案可读,我省略了任何类型的异常处理。但是,在您的实际代码中,您必须负责处理异常处理以及涉及失败或中断下载的情况!

要有8个并行下载显示其在相应的8个进度条中的进度,您只能从8个活动下载开始,任何进一步的现有下载作业都被视为待处理。当其中一个活动下载完成时,将启动另一个待处理下载作业,同时将其与用于完成下载的进度条相关联。 (这里给出的代码示例是以与任意数量的进度条一起操作的方式完成的,而不仅仅是8个。)

所有待处理的下载作业都将存储在队列中。要开始下载,请取出下载作业并从队列中删除。

private readonly Queue<DownloadData> _downloadQueue = new Queue<DownloadData>();

(注意private readonly,它允许将此对象用作同步对象,如稍后所述。)

DownloadData 是一种简单类型,包含每个下载作业的所有必要信息。

public class DownloadData
{
    public Uri DownloadUri;
    public string TargetPath;

    public DownloadData(Uri downloadUri, string targetPath)
    {
        this.DownloadUri = downloadUri;
        this.TargetPath = targetPath;
    }
}


现在,这个队列是如何填充的?代码基本上已存在于ExtractImages类中。但是,不是填写两个列表 imagesSatelliteUrls imagesRainUrls ,而是填充队列:

public static void AddImageDownloadsToQueue(Queue<DownloadData> downloadQueue, List<string> FirstTags, List<string> LastTags, List<string> Maps, string LocalFileDir, string UrlsDir)
{
    for (int i = 0; i < Maps.Count; i++)
    {
        string imageSatelliteUrl = ... // compose URL for satellite image

        downloadQueue.Enqueue(
            new DownloadData(
                new Uri(imageSatelliteUrl),
                UrlsDir + "SatelliteImage" + x.ToString("D6")
            )
        );

        string imageRainUrl = ... // compose URL for rain image

        downloadQueue.Enqueue(
            new DownloadData(
                new Uri(imageRainUrl),
                UrlsDir + "RainImage" + x.ToString("D6")
            )
        );
    }
}


使用用于填充队列的方法,只需要调用它并最终处理队列。下载队列的处理在两个地方/两个地方发生:(1)如果其中一个活动下载完成则必须启动另一个下载项目,以及(2)根据可用进度的数量最初开始下载次数吧(=下载位置)。

前者将在稍后介绍。后者与调用 AddImageDownloadsToQueue 方法一起将在 backgroundWorker1_DoWork 中发生:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    lock(_downloadQueue)
    {
        AddImageDownloadsToQueue(_downloadQueue, StartTags, LastTags, Maps, localFilename, UrlsPath);

        if (_downloadQueue.Count > 0)
            foreach (ProgressBar pb in progressbars)
            {
                if (pb.Tag == null)
                {
                    DownloadData dd = _downloadQueue.Dequeue();
                    StartDownloadWithProgressBar(dd, pb);

                    if (_downloadQueue.Count == 0) break;
                }
            }
    }
}

你会在这里注意到三件事。首先,使用 lock 来同步对下载队列的访问,避免潜在的并发访问和竞争条件。请注意,使用 _downloadQueue 作为锁定对象是安全的,并且只是安全的,因为它被声明为private readonly

其次,测试 if(pb.Tag == null)完成。 ProgressBar的Tag属性在这里(ab)用作标志,用于指示进度条是否已用于下载。如果其值为null,则表示它未被使用。任何其他值(非null)表示,它目前用于下载。假设您可能会在多个块中排队下载项目,这种方法可确保始终使用所有可用的进度条,无论何时将新的下载项添加到队列中。

第三个也是最重要的是方法 StartDownloadWithProgressBar 。此方法设置WebClient对象并开始下载。

private void StartDownloadWithProgressBar(ExtractImages.DownloadData downloadData, ProgressBar progressBar)
{
    WebClient wc = new WebClient();
    wc.DownloadFileCompleted += DownloadCompletedCallback;
    wc.DownloadProgressChanged += DownloadProgressCallback;

    ActiveDownloadJob adJob = new ActiveDownloadJob(downloadData, progressBar, wc);
    progressBar.Tag = adJob;
    wc.DownloadFileAsync(
        downloadData.DownloadUri,
        downloadData.TargetPath,
        adJob
    );
}

虽然WebClient.DownloadFileAsync用于下载文件,但是一个WebClient对象无法处理多个并发下载。因此,每次下载都需要自己的WebClient实例。两个回调处理WebClient的DownloadFileCompleted和DownloadProgressChanged事件。这些回调将在稍后介绍。

创建一个 ActiveDownloadJob 对象,它跟踪下载作业及其关联的ProgressBar和WebClient(虽然下面列出的示例代码不再需要访问WebClient,但它可能是对于将来扩展而言实用,可以参考手头的WebClient实例。)

ActiveDownloadJob 已分配到进度条的标记属性,此操作表示进度条正在使用中。此外, ActiveDownloadJob 对象作为 UserToken 参数传递给DownloadFileAsync方法。这样做是为了使回调方法知道在(正在进行的)下载调用时要操作的进度条。

ActiveDownloadJob是一个非常简单的类:

    class ActiveDownloadJob
    {
        public DownloadData DownloadData;
        public ProgressBar ProgressBar;
        public WebClient WebClient;

        public ActiveDownloadJob(ExtractImages.DownloadData downloadData, ProgressBar progressBar, WebClient webClient)
        {
            this.DownloadData = downloadData;
            this.ProgressBar = progressBar;
            this.WebClient = webClient;
        }
    }


完成下载的回调方法( DownloadCompletedCallback )必须注意两件事:检查已下载/失败的下载并启动队列中的下一个下载作业。

private void DownloadCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
    if (e.Cancelled)
    {
         ... download cancelled...
    }
    else if (e.Error != null)
    {
         ... download failed...
    }

    ActiveDownloadJob adJob = e.UserState as ActiveDownloadJob;
    ProgressBar pb = (adJob != null) ? adJob.ProgressBar : null;

    lock (_downloadQueue)
    {
        if (_downloadQueue.Count == 0)
        {
            if (pb != null) pb.Tag = null;
        }
        else
        {
            DownloadData dd = _downloadQueue.Dequeue();
            StartDownloadWithProgressBar(dd, pb);
        }
    }
}

请注意,通过锁定到方法 backgroundWorker1_DoWork 中发生的同一对象来同步对队列的访问。此外,请注意此方法如何获取下载的 ActiveDownloadJob 对象实例。请记住, ActiveDownloadJob 对象作为 UserToken 参数传递给 DownloadFileAsync 方法?它再次浮出水面。

如果队列中没有其他下载作业,则进度条的标记属性设置为null,表示ProgressBar空闲且可再次使用,否则将执行新的下载作业从队列中调用 StartDownloadWithProgressBar 启动。 StartDownloadWithProgressBar会将进度条的标记属性设置为新下载的 ActiveDownloadJob 对象。

无论在哪种情况下,始终确保已完成下载的 ActiveDownloadJob 对象从进度条的Tag属性中删除,因此它最终将保留在WebClient中是GC&#39; ed。


虽然我们差不多完成了,但仍然缺少一些内容:下载过程中需要更新进度条。这种情况发生在下载进程的回调方法中:

private void DownloadProgressCallback(object sender, DownloadProgressChangedEventArgs e)
{
    ActiveDownloadJob adJob = e.UserState as ActiveDownloadJob;
    if (adJob != null && adJob.ProgressBar != null)
        adJob.ProgressBar.Invoke((Action) (() => adJob.ProgressBar.Value = e.ProgressPercentage));
}

DownloadProgressCallback 以与 DownloadCompletedCallback 类似的方式获取属于下载的ProgressBar对象。


如果你需要知道所有下载何时完成,这里有一个提示:如果队列为空并且没有下载活动,那么所有下载都已完成(或者,没有下载开始...)