最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】给C#程序添加滚动进度条(ProgressBar),实现滚动/动态更新

C# crifan 12955浏览 0评论

【问题】

之前写了个C#的小程序:

downloadSontasteMusic,

实现ST中的歌曲下载,但是现在下载歌曲过程中,没有下载进度的显示,所以用户体验很不好。

现在想要添加进度条。

【解决过程】

1.其实,关于C#中添加进度条的事情,之前就折腾过:

【未解决】C#中添加始终滚动的进度条(跑马灯)和一格一格前进的滚动条(块)

只是没有解决。

现在打算继续去折腾折腾,看看是否能搞定。

2.看了半天,还是去参考微软官网的:

ProgressBar.Increment 方法

去尝试添加代码,看看能否有效果。

最后试了半天,添加了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System.Windows.Forms;
 
private Timer downloadTimer;
 
private void IncreaseDownloadProgressBar(object sender, EventArgs e)
{
    // Increment the value of the ProgressBar a value of one each time.
    int currentPecent = getDownloadPercent();
 
    //pgbDownload.Increment(1);
    pgbDownload.Increment(currentPecent);
 
    // Determine if we have completed by comparing the value of the Value property to the Maximum value.
    if (pgbDownload.Value == pgbDownload.Maximum)
        // Stop the timer.
        downloadTimer.Stop();
}
 
// Call this method from the constructor of the form.
private void InitializeDownloadTimer()
{
    downloadTimer = new Timer();
    //downloadTimer.Enabled = true;
    // Set the interval for the timer.
    downloadTimer.Interval = 100;
    // Connect the Tick event of the timer to its event handler.
    downloadTimer.Tick += new EventHandler(IncreaseDownloadProgressBar);
    // Start the timer.
    downloadTimer.Start();
}
 
public void downloadStMusicFromUrl()
{
...
    InitializeDownloadTimer();
 
    //download it
    if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
    {
        showCompleteHint();
    }
}

结果运行的效果只是,下载过程中,还是死掉了,只有下载完毕,对应的timer的函数,才会被调用,所以还是没用。

看来还是需要另外建立一个线程,实现更新进度的效果才行。

3.还是去参考:

C# Progress Bar

然后去折腾了半天,如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
private delegate void ProgressBarShow(int i);
private void ShowPro(int currentProgress)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new ProgressBarShow(ShowPro), currentProgress);
    }
    else
    {
        this.pgbDownload.Value = currentProgress;
    }
}
 
private void updateProgress()
{
    int currentPecent = 0;
    while (currentPecent <= 100)
    {
        //this.ShowPro(currentPecent);
        //currentPecent++; //模拟发送多少
        //Thread.Sleep(10);
 
        currentPecent = getDownloadPercent();
        ShowPro(currentPecent);
        Thread.Sleep(250);
    }
    Thread.CurrentThread.Abort();
}
 
 
private void initForDownloadProcess()
{
    Thread thread = new Thread(new ThreadStart(updateProgress)); //模拟进度条
    thread.IsBackground = true;
    thread.Start();
}
 
public void downloadStMusicFromUrl()
{
    string singleStUrl = "";
 
    //get st url
    singleStUrl = txbStUrl.Text;
 
    //InitializeDownloadTimer();
    initForDownloadProcess();
 
    //download it
    if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
    {
        showCompleteHint();
    }
}

实现出来的效果,也是最后一次性更新100%,即界面卡死,最后一次性全部更新完毕,而无法实现动态绘制更新进度条。

4.后来是看到

C# Winform下载文件并显示进度条

中有个

System.Windows.Forms.Application.DoEvents();

所以无意间去虽然找了找相关介绍,找到这个:

关于Application.DoEvents()

然后随便尝试把

System.Windows.Forms.Application.DoEvents();

加到我的此处底层最耗时下载数据的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public int getUrlRespStreamBytes(ref Byte[] respBytesBuf,
                                string url,
                                Dictionary<string, string> headerDict,
                                Dictionary<string, string> postDict,
                                int timeout)
{
    int curReadoutLen;
    int curBufPos = 0;
    int realReadoutLen = 0;
 
    try
    {
        //HttpWebResponse resp = getUrlResponse(url, headerDict, postDict, timeout);
        HttpWebResponse resp = getUrlResponse(url, headerDict, postDict);
        int expectReadoutLen = (int)resp.ContentLength;
 
        totalLength = expectReadoutLen;
        currentLength = 0;
 
        Stream binStream = resp.GetResponseStream();
        //int streamDataLen  = (int)binStream.Length; // erro: not support seek operation
 
        do
        {
            System.Windows.Forms.Application.DoEvents();
 
 
            // here download logic is:
            // once request, return some data
            // request multiple time, until no more data
            curReadoutLen = binStream.Read(respBytesBuf, curBufPos, expectReadoutLen);
            if (curReadoutLen > 0)
            {
                curBufPos += curReadoutLen;
 
                currentLength = curBufPos;
 
                expectReadoutLen = expectReadoutLen - curReadoutLen;
 
                realReadoutLen += curReadoutLen;
            }
        } while (curReadoutLen > 0);
    }
    catch
    {
        realReadoutLen = -1;
    }
 
    return realReadoutLen;
}

然后结果竟然是就成功了,可以及时动态刷新进度条了:

20 percent

 

25 percent

90 percent

5.后来又继续验证,发现原先的使用timer的方法,其实也是有效的。

只要是底层的,最耗时的那个循环中有了:

System.Windows.Forms.Application.DoEvents();

此时timer就可以得到响应,就可以及时获得进度,及时刷新进度条了。

所以,此处证明了,是由于底层的耗时的函数,使得界面没得响应,而在耗时函数中的循环中,加上

System.Windows.Forms.Application.DoEvents();

后,上层界面中的内容,才能得以更新的。

 

【总结】

对于想要实现动态滚动刷新进度条的话,则除了更新进度相关的代码的话,不论是用另外一个进程实现,还是用一个timer实现,则都不是没用的,都还是会导致界面卡死的。

因为另外一个耗时的函数,是需要一次性执行完,才能继续执行余下的函数的。

(注:相关解释参考:关于Application.DoEvents()

所以,必须另外在底层函数函数中的循环里面,添加上:

System.Windows.Forms.Application.DoEvents();

此时,上层的界面的更新,包括进度条更新,才能起效果的。

 

此处再完整的解释一下,具体实现的机制:

首先,我原先的代码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void downloadStMusicFromUrl()
{
    string singleStUrl = "";
 
    //get st url
    singleStUrl = txbStUrl.Text;
 
    //download it
    if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
    {
        showCompleteHint();
    }
}

其中,downloadSingleStMusic是那个最耗时的函数,其底层是调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
public int getUrlRespStreamBytes(xxx)
{
...
    do
    {
        System.Windows.Forms.Application.DoEvents();
 
...
        curReadoutLen = binStream.Read(respBytesBuf, curBufPos, expectReadoutLen);
...
    } while (curReadoutLen > 0);
...
}

中的binStream.Read,去读取网络返回的数据的,这部分是比较耗时的,尤其是需要获取整个MP3文件的数据。

而此时,如果不加上述的:

System.Windows.Forms.Application.DoEvents();

则会导致,此函数必须一次性执行完毕,然后上层的函数,才能接着执行后续的

showCompleteHint();

此过程,整个UI部分就是卡死掉的,无法操作的,无法更新进度条的。

 

在底层添加了对应的

System.Windows.Forms.Application.DoEvents();

之后,上层可以通过两种方式实现对应的进度条的更新:

1.线程法

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private delegate void ProgressBarShow(int i);
private void ShowPro(int currentProgress)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new ProgressBarShow(ShowPro), currentProgress);
    }
    else
    {
        this.pgbDownload.Value = currentProgress;
    }
}
 
private void updateProgress()
{
    int currentPecent = 0;
    while (currentPecent < 100)
    {
        currentPecent = getDownloadPercent();
        ShowPro(currentPecent);
        Thread.Sleep(100);
    }
 
    Thread.CurrentThread.Abort();
}
 
private void initForDownloadProcess()
{
    Thread thread = new Thread(new ThreadStart(updateProgress)); //模拟进度条
    thread.IsBackground = true;
    thread.Start();
}
 
public void downloadStMusicFromUrl()
{
    string singleStUrl = "";
 
    //get st url
    singleStUrl = txbStUrl.Text;
 
    //initializeDownloadTimer();
    initForDownloadProcess();
 
    //download it
    if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
    {
        pgbDownload.Value = 0;
        //pgbDownload.Update();
        System.Windows.Forms.Application.DoEvents(); // must add this, otherwise the UI not update !!!
        showCompleteHint();
    }
}

 

2.Timer法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
private void increaseDownloadProgressBar(object sender, EventArgs e)
{
    // Increment the value of the ProgressBar a value of one each time.
    int currentPecent = getDownloadPercent();
 
    //pgbDownload.Increment(1);
    //pgbDownload.Increment(currentPecent);
    pgbDownload.Value = currentPecent;
 
    // Determine if we have completed
    if (pgbDownload.Value >= pgbDownload.Maximum)
        // Stop the timer.
        downloadTimer.Stop();
}
 
// Call this method from the constructor of the form.
private void initializeDownloadTimer()
{
    downloadTimer = new System.Windows.Forms.Timer();
    //downloadTimer.Enabled = true;
    // Set the interval for the timer.
    downloadTimer.Interval = 100;
    // Connect the Tick event of the timer to its event handler.
    downloadTimer.Tick += new EventHandler(increaseDownloadProgressBar);
    // Start the timer.
    downloadTimer.Start();
}
 
public void downloadStMusicFromUrl()
{
    string singleStUrl = "";
 
    //get st url
    singleStUrl = txbStUrl.Text;
 
    initializeDownloadTimer();
    //initForDownloadProcess();
 
    //download it
    if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
    {
        pgbDownload.Value = 0;
        //pgbDownload.Update();
        System.Windows.Forms.Application.DoEvents(); // must add this, otherwise the UI not update !!!
        showCompleteHint();
    }
}

其中:

  • 更新进度条的方式,此处使用直接设置值去更新的:pgbDownload.Value = currentPecent;,你也可以根据自己需求,使用相关的pgbDownload.Increment(1);去增加相应的进度
  • 此种方法,相对比较简单,更容易理解。

 

对于上述两种方法中相同部分的代码的解释:

  • pgbDownload是ProgressBar变量;
  • getDownloadPercent()用于获得底层已获得的数据的比例,你根据需要,改为自己相关的函数即可;
  • 在使用downloadSingleStMusic下载完毕歌曲之后,使用:
    • pgbDownload.Value = 0;
    • 同样无法清空进度条的100的效果,只能再添加:
    • System.Windows.Forms.Application.DoEvents();
    • 才能实现清空进度条,变成0的效果。

 

另外,据关于Application.DoEvents()中的讨论,说是Application.DoEvents会导致效率低的问题。此处暂时不去深究。等有空再研究。

转载请注明:在路上 » 【已解决】给C#程序添加滚动进度条(ProgressBar),实现滚动/动态更新

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (2)

  1. 下载的时候用异步接口或者自己另外建一个线程实现异步效果是不错的。
    Joe13年前 (2012-10-02)回复
    • 问题的核心在于,由于底层函数长期运行,会导致界面卡死,之前不知道要加那个Application.DoEvents(),所以无论上层用何种方法,都还是界面卡死,无法实现效果的。只有加了Application.DoEvents(),我这里界面才得以更新,才能看到进度条更新的效果的。
      crifan13年前 (2012-10-03)回复
87 queries in 0.730 seconds, using 22.21MB memory