----哆啦刘小洋 原创,转载需说明出处
MFC高级通用进度条-目录
- 1 简介
- 2 进度条实现基本原理
- 3 使用方法
- 3 .1 简单情况
- 3.2 两个前后独立的进度条
- 3.3 实际应用的一般情况
- 3.4 带子进度条
- 4 其他
- 4.1 使用限制
- 4.2 其他全局宏(函数)
- 4.3 当前进度条设置
1 简介
SegeX组件之一:SegeX EvProgress。应用级源代码,首次公开。EvProgress是基于MFC的通用进度条,功能强大又简单易用。几行代码就可以让你的计算具有进度显示的功能,并且具有多种高级功能:1. 支持任意多个进度条同时存在;2.支持在子函数中继续主函数中的进度;3.支持两级子进度条;4.加入进度条后可避免长时间计算的”应用程序没有响应”;5.支持调节大小、背景颜色、背景图片;6.全自动管理。
源代码下载附后。
2 进度条实现基本原理
“工程地球物理系统(SegeX)“公开的原创组件之一。
本篇文章并不打算深入到实现原理的末端细节,感兴趣的读者可以阅读源代码,这里仅从宏观介绍一下实现原理。
1)全局模式。组件利用专门的处理类CEvProgressManager,管理任意多个进度条窗口CEvProgressWin。用户接口使用全局宏EV_START_NEW_PROGRESS_AUTO()来声明一个新的进度条。
2)栈管理。当声明一个新的进度条后,先前的进度条被压栈,因此这之后所有的进度操作都是针对当前的进度条。
3)自动释放。EV_START_NEW_PROGRESS_AUTO声明时,后台组件辅助类CEvProgressAutoDestroy会记录当前进度条,当EV_START_NEW_PROGRESS_AUTO所在的作用域结束时,CEvProgressAutoDestroy自动释放本次声明的进度条。
4)子函数中仍然可以继续主函数的进度,且子函数与主函数完全脱耦,无参数传递,不用关心也不需要知道是谁调用,也不用关心进度占主进度多少(按100%处理即可)。
5)全局设置。组件利用全局函数设置进度条的尺寸、背景颜色、背景图片等全局参数。
6)局部设置。组件在新开进度条之后,也可以对该进度条进行专门的背景图片设置等。
3 使用方法
首先,该组件是针对MFC Windows桌面应用程序,不支持控制台程序。因此假定你的工程是MFC Windows桌面应用程序,例如对话框程序。
1)将ev_progress.h 、ev_progress.cpp复制到工程指定目录。
2)在要使用的文件中增加头文件:#include “ev_progress.h”
3)ev_progress.cpp会用到预编译头文件,如果这里出错,请改为你的工程预编译头文件。(vc6一般是stdafx.h,而VC2022是pch.h)
4)然后可以开始使用了。详见后续多种使用场景。
3 .1 简单情况
最简单也是最常用的使用场景是在一个函数中,需要长时间计算,这时给一个进度条可提升交互体验,并可避免长时间纯计算引起的计算机假死(应用程序没有响应)现象。
举例说明:
void test_Progress_Simple()
{
int i;
//开始一个进度条
EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);
for (i = 0; i < 10000; ++i)
{
//设置进度条进度
EV_DO_PROGRESS_CHECK_ABORT(i * 1.0 / 10000, NULL, NULL)
return; //如果点击了“取消”按钮
EV_END_DO_PROGRESS_CHECK_ABORT()
}
}
代码中主要有三行控制进度条的代码:
1)EV_START_NEW_PROGRESS_AUTO(lpstrTitle, lpstrMainPrompt, lpstrSubPrompt, dStart, dEnd, nShowSub):申明一个新的进度条。
lpstrTitle:进度条窗口的标题
lpstrMainPrompt:主进度条提示
lpstrSubPrompt:子进度条提示
dStart:主进度起始值。组件的进度基于0~1。
dEnd:主进度结束值。
nShowSub:是否显示子进度。0:不显示,1:显示。
2)EV_DO_PROGRESS_CHECK_ABORT(dValue, lpstrMainPrompt, lpstrSubPrompt):设置进度条进度的语句。
dValue:进度值。进度基于0~1。
lpstrMainPrompt:主进度条提示。这里可以改变它,不需要改变,设为NULL。
lpstrSubPrompt:子进度条提示。这里可以改变它,不需要改变,设为NULL。
3)EV_END_DO_PROGRESS_CHECK_ABORT():结束进度条进度的设置
组件支持取消操作,因此在这两句中间的代码,就是点击了“取消”之后,你需要执行的代码。如果不允许取消,两句中间可以空着。
组件在执行EV_DO_PROGRESS_CHECK_ABORT时,会处理Windows消息队列,防止消息堵塞,因此可以防止计算机没有响应的现象。
3.2 两个前后独立的进度条
组件支持顺序任意多个进度条,操作总是针对最后一个进度条操作。
举例说明:
//两个前后独立的进度条
void test_Progress_2()
{
int i, j;
//用应用程序的主图标作为进度条窗口图标
EV_SET_PROGRESS_WINDOW_ICON(128);//IDR_MAINFRAME
//开始一个进度条
EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);
for (i = 0; i < 10000; ++i)
{
//插入一个新的进度条
if (i == 0)
{
EV_START_NEW_PROGRESS_AUTO(_T("Title2"), _T("Prompt2..."), NULL, 0, 1, 0);
for (j = 0; j < 10000; ++j)
{
EV_DO_PROGRESS_CHECK_ABORT(j * 1.0 / 10000, NULL, NULL)
; //如果点击了“取消”按钮,什么也不做(也即是不能取消)
EV_END_DO_PROGRESS_CHECK_ABORT()
}
}
//继续第一个进度条
EV_DO_PROGRESS_CHECK_ABORT(i * 1.0 / 10000, NULL, NULL)
return; //如果点击了“取消”按钮,终止函数运行
EV_END_DO_PROGRESS_CHECK_ABORT()
}
}
代码在新建一个进度条之后,马上建立了第二个进度条并操作,第二个进度条结束后,继续第一个进度条的操作。
同时该段代码用EV_SET_PROGRESS_WINDOW_ICON()函数设置了一下窗口的图标。你需要根据你的工程设置图标资源号。
3.3 实际应用的一般情况
实际应用中一般经常遇到进程分几段,每段会占用总进度一定占比,而每段又是调用的子函数,比如下面的示意性代码:
void xxx()
{
//第1段 占总进度的 %0 ~ %30
Sub_Function1();
//第2段 占总进度的 %30~100%
Sub_Function2();
}
想实现上述功能,使用EvProgress将变得十分简单,实例如下:
//一般情况
bool Sub_Function1(int n)
{
int j;
for (j = 0; j < n * 1000; j++)
{
//只需关心本过程的进度值
EV_DO_PROGRESS_CHECK_ABORT(j * 1.0 / (n * 1000), NULL, NULL);//按占比100%进度处理
return false;
EV_END_DO_PROGRESS_CHECK_ABORT();
}
return true;
}
void test_Progress_Normal()
{
//开始一个进度条
EV_START_NEW_PROGRESS_AUTO(_T("Title"), _T("Prompt..."), NULL, 0, 1, 0);
//第1段 %0~%30
EV_SET_PROGRESS_RANGE(0, 0.3); //设置后续进度在总进度的占比范围
Sub_Function1(30);
//第2段 %30~100%
EV_SET_PROGRESS_RANGE(0.3, 1); //设置后续进度在总进度的占比范围
Sub_Function1(70);
}
如代码所示,在第一段,只需要用EV_SET_PROGRESS_RANGE设置该段在总进度的占比范围,然后调用子函数即可。而在子函数呢,只需按照100%的进度去赋进度值即可。第二段和第一段一样,只需要先设置该段在总进度的占比范围(0.3, 1)即可。
3.4 带子进度条
有时计算时间很长,主函数调用子函数,或主循环中带大计算量的子循环,为了防止看上去一动不动,组件支持一级子进度。
举例说明:
//带子进度条
bool Sub_Function();
void test_Progress_Sub()
{
int i;
EV_START_NEW_PROGRESS_AUTO(_T("waiting..."), _T("Main:"), _T("Sub"), 0.0, 1.0, 1/*子进度条数为1(1个子进度*/);
for(i =0 ; i< 50; i++)
{
//因为外层循环是50次,所以每次子循环占据主进度1/50。模式为按比例。
EV_START_SUB_PROGRESS(1.0/50/*本次子进度占主进度设置范围的1/50*/,
EV_PROGRESS_MODE_SCALE_ABSOLUTE/*子进度按主进度占比通知主进度*/);
if(!Sub_Function())
return;
EV_END_SUB_PROGRESS();
}
}
bool Sub_Function()
{
int j;
for(j =0; j< 1000; j++)
{
//只需关心本过程的进度值
EV_DO_PROGRESS_CHECK_ABORT((j*1.0 / 999), NULL, NULL);
return false;
EV_END_DO_PROGRESS_CHECK_ABORT();
}
return true;
}
EV_START_SUB_PROGRESS(dRange, nMode):开始一个子进度。
dRange:子进度占总进度值(0~1)。
nMode:
EV_PROGRESS_MODE_SCALE_ABSOLUTE:占主进度总范围比例(取值:0~1)
EV_PROGRESS_MODE_RANGE_ABSOLUTE:占主进度初始范围值(取值:0~1)
注意EV_DO_PROGRESS_CHECK_ABORT()总是针对当前计算过程中赋值。因此组件使用非常灵活实用。比如一个子过程可能被2个主函数调用(上面代码中Sub_Function作为一个子函数),并且占2个主函数的计算量不一样,但通过上述代码,只需主函数指定子函数的进度比,子函数就可以在2个不同地方良好工作。
甚至还有第三个主函数调用了子函数,但主函数并没有申请进度条,子函数中的EV_DO_PROGRESS_CHECK_ABORT()也会正常工作,只是什么都不做而已,但不会引起程序崩溃。
4 其他
4.1 使用限制
在一个作用域(就是一对{})里,只能声明一次EV_START_NEW_PROGRESS_AUTO()。例如下代码会产生编译错误:
void test_error()
{
EV_START_NEW_PROGRESS_AUTO(_T("waiting1..."), _T("Main1:"), _T("Sub1"), 0.0, 1.0, 1);
//...
EV_START_NEW_PROGRESS_AUTO(_T("waiting2..."), _T("Main2:"), _T("Sub2"), 0.0, 1.0, 1);
}
增加一个作用域,可以解决,代码如下:
void test_error()
{
EV_START_NEW_PROGRESS_AUTO(_T("waiting1..."), _T("Main1:"), _T("Sub1"), 0.0, 1.0, 1);
//...
{
EV_START_NEW_PROGRESS_AUTO(_T("waiting2..."), _T("Main2:"), _T("Sub2"), 0.0, 1.0, 1);
}
}
4.2 其他全局宏(函数)
必须放在EV_START_NEW_PROGRESS_AUTO之前, 之后对所有进度条有效:
// 1)EV_SET_PROGRESS_WINDOW_ICON(UINT idIcon);//指定进度条对话框图标
// 2)EV_SET_PROGRESS_CONFIRM_CANCEL(BOOL bConfirm); //指定取消是否需要确认
// 3)EV_SET_PROGRESS_COLOR(DWORD forecolor, DWORD backcolor); //指定进度条前景色和背景色
// 4)EV_SET_PROGRESS_CTRL_SIZE(int width, int height); //指定进度条的宽度和高度
// 5)EV_SET_PROGRESS_BITMAP(UINT idFore, UINT idBack); //指定全局进度条的位图
4.3 当前进度条设置
必须放在EV_START_NEW_PROGRESS_AUTO之后,对当前进度条有效:
// 1)EV_SET_PROGRESS_STRINGS(LPCTSTR lpstrTitle, LPCTSTR lpstrMainPrompt, LPCTSTR lpstrSubPrompt);//指定显示的文本
// 2)EV_SET_SHOW_SUBPROGRESS(int nSubMark = 0, BOOL bUpdateWin = TRUE);
// 3)EV_GET_PROGRESS_RANGE(double& dStart, double& dEnd);//获取进度限定范围
// 4)EV_SET_PROGRESS_BITMAP_CURRENT(IDB_BITMAP_MAIN[, IDB_BITMAP_SUB]);//指定当前进度条的位图(优先于全局)
下载完整的代码资源。代码资源附带完整的VC实例工程。