前言
- 因为做的项目需要用到进度条表示进度,同时用label显示当前进度的值,于是乎,采用常规方式试了一下。
- 然后问题就来了!!!朋友!
- 我们知道,进度条的值表示方法是通过修改其value属性的值来进行的。
//i为进度,默认范围为0-100
ProgressBar.value = i;
- 乍一看感觉没什么问题,处理的循环中,计算出n的值,赋予进度条的value,进度条就显示对应的进度。
- 同时,为了显示进度的具体值,我们还是用label进行展示n的值。
Label.Text = i.ToString();
- 这样看起来似乎也没问题,那我们开始同步更新进度条和label的显示呢。
ProgressBar.value = i;
Label.Text = i.ToString();
- 好像还是没有问题,但实际上运行呢?
- 实际运行的时候,我们就会发现,如果运行速度很快的话,即进度很快就满100%的时候,进度条跑满,但是label的值直接就从一开始的0%变为了100%,有朋友有可能会说,是因为太快了,来不及显示中间的进度值,所以只看到了100%,那我们先进行一点小的修改。
for(int i = 0;i <= 100;i++)
{
ProgressBar.value = i;
Label.Text = i.ToString();
Thread.Sleep(1000);//等待1000毫秒
}
- 我们给循环增加一个延时,延时一秒钟,理论上我们就可以看到进度条和显示值应该同时从0%,1%,2%...100%这样进行变化。
- 我们启动程序开始运行,运行后发现,诶?为什么我只有进度条在一格一格的增加,但是label没变呢,我们耐心的等待进度条跑满,发现跑满的一瞬间,label的值变成了100%。
- 也就是说,其实label的Text属性是接收到了我们给它的值,只是它没有显示出来,等进度条跑完了,它才显示出来。
- 但是我们想要的效果是进度条和值一起改变,并且,细心的朋友也会发现,进度条在改变的过程中,我们的界面好像也卡死了,只有进度条跑满了,界面才恢复正常。
- 这是因为此时我们的程序都还在使用单线程,当线程专心去修改进度条的时候,界面就卡住了。
解决方案
- 屁话了这么久,解决方案终于开始出来了。
- 根据前一段的分析,我们知道主界面卡住的原因是因为线程占用,导致,所以我们采用多线程,用一个子线程来控制进度条的显示。
ThreadStart childref = new ThreadStart(ProgressBarSetting);//新线程
Thread childThread = new Thread(childref);
childThread.IsBackground = true;//后台线程
childThread.Start();
- 上述代码我们生成了一个新的子线程,用子线程处理ProgressBarSetting函数
private void ProgressBarSetting()
{
int i;
for (i = 0; i <= 1000; i++)
{
setPos(i);//设置进度条的值
ProgressValues(i);//设置label显示的值
Thread.Sleep(100);
}
}
- 有朋友肯定注意到,我这里设置进度条的值和设置label的值都是通过调用的函数,而不是直接设置。
- 我暂时不解释原因,先说我们修改为多线程后,直接运行的情况,理想状态应该就是进度条增加,label的值与进度条同步增长。
- 然而,我们运行之后会发现,界面依然卡住,进度条自己跑自己的,label的值没有变化,似乎多线程没有生效,开启单步调试,发现label的值其实是被更改了的,问题似乎又回到了最开始的样子,并且,如果我们没有解除跨线程检测的话,甚至开始运行程序的时候,就会有异常出现。IDE会告诉你,ProgressBar不是当前线程创建的,不能修改它的值。
- 这个问题我们可以通过解除跨线程检测来处理,或者通过委托的方式。
解除跨线程检测
- 顾名思义,程序会自动进行跨线程检测,对于直接的跨线程操作,就会抛出异常,我们可以主动关闭它,来达到自己的目的。
CheckForIllegalCrossThreadCalls = false;
委托函数
- 关于委托函数←点击查看
- 此处我们也会用到委托函数来进行安全的跨线程操作
private delegate void DelegateFunction(int ipos);//定义委托
- 到了这里,我终于可以解释为什么前文中要使用setPos函数来进行进度条值的修改了。先看看代码
private void setPos(int ipos)
{
if (this.ProgressBar.Control.InvokeRequired)//当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它
{
DelegateFunction df = new DelegateFunction(setPos);
this.Invoke(df, new object[] { ipos });
}
else
{
ProgressBar.Value = ipos;
}
}
private void ProgressValues(int i)
{
i = i / 10;
Label.Text = $"{i.ToString()}%";
}
- 通过这样的方式,我们在需要修改进度条值的时候才进行Invoke,而不是一直在线程中占用它,这样就可以防止界面卡死,而label也可以同步的显示当前的进度了。
其它
- 实现同样效果的方法有很多,例如使用BackgroundWorker控件,或者Application.DoEvent()方法,本文不再叙述,请自行学习!
- 我的文章对你有帮助吗,喜欢的话不妨打赏我一下吧!
最后一次更新于2019-11-27
0 条评论