【问题】
之前写了个C#的小程序:
实现ST中的歌曲下载,但是现在下载歌曲过程中,没有下载进度的显示,所以用户体验很不好。
现在想要添加进度条。
【解决过程】
1.其实,关于C#中添加进度条的事情,之前就折腾过:
【未解决】C#中添加始终滚动的进度条(跑马灯)和一格一格前进的滚动条(块)
只是没有解决。
现在打算继续去折腾折腾,看看是否能搞定。
2.看了半天,还是去参考微软官网的:
去尝试添加代码,看看能否有效果。
最后试了半天,添加了如下代码:
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.还是去参考:
然后去折腾了半天,如下的代码:
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.后来是看到
中有个
System.Windows.Forms.Application.DoEvents();
所以无意间去虽然找了找相关介绍,找到这个:
然后随便尝试把
System.Windows.Forms.Application.DoEvents();
加到我的此处底层最耗时下载数据的部分:
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;
}然后结果竟然是就成功了,可以及时动态刷新进度条了:
5.后来又继续验证,发现原先的使用timer的方法,其实也是有效的。
只要是底层的,最耗时的那个循环中有了:
System.Windows.Forms.Application.DoEvents();
此时timer就可以得到响应,就可以及时获得进度,及时刷新进度条了。
所以,此处证明了,是由于底层的耗时的函数,使得界面没得响应,而在耗时函数中的循环中,加上
System.Windows.Forms.Application.DoEvents();
后,上层界面中的内容,才能得以更新的。
【总结】
对于想要实现动态滚动刷新进度条的话,则除了更新进度相关的代码的话,不论是用另外一个进程实现,还是用一个timer实现,则都不是没用的,都还是会导致界面卡死的。
因为另外一个耗时的函数,是需要一次性执行完,才能继续执行余下的函数的。
(注:相关解释参考:关于Application.DoEvents())
所以,必须另外在底层函数函数中的循环里面,添加上:
System.Windows.Forms.Application.DoEvents();
此时,上层的界面的更新,包括进度条更新,才能起效果的。
此处再完整的解释一下,具体实现的机制:
首先,我原先的代码是:
public void downloadStMusicFromUrl()
{
string singleStUrl = "";
//get st url
singleStUrl = txbStUrl.Text;
//download it
if (downloadSingleStMusic(singleStUrl, txbSaveTo.Text))
{
showCompleteHint();
}
}其中,downloadSingleStMusic是那个最耗时的函数,其底层是调用:
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.线程法
完整代码如下:
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法
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会导致效率低的问题。此处暂时不去深究。等有空再研究。


