程序进度指示的设计
动机:
我常常需要写一些很费时的算法函数。为了不让用户失去耐心,我需要一个进程条来指示程序运行的进度。然而算法函数是写在一个界面无关的模块里,我不可能把具体进程条的类暴露给它。算法函数也很少是一个简单的循环,它通常要调一些子函数,有些子函数也非常的费时。如果我让用户等了n分钟然后告诉用户程序运行了**%,那我给出进程条的意义就不大了,我需要实现均匀的进度指示。
接口:
通常进程条的实现是在界面模块里,为了不将具体进程条的类暴露给算法模块,我将进程条定义成下面的接口。
struct IProgress{
virtual void operator += (float r)=0;
virtual void operator =(const char* str)=0;
virtual bool operator!()const=0;
};
接口中各个函数意义如下:
一:+=操作符用来告诉用户程序前进了r。这里我们约定整个程序运行完算前进了100。
二:=操作符用来给出一些程序正在做什么事情的信息。
三:! 操作符用来告诉算法函数,用户是否取消了此项任务。
界面模块进程条的类必须实现这个接口,然后把它传给算法模块。由于算法模块只关心这个接口,所以界面模块可以随时改变进程条的风格而不必担心会对算法模块有任何影响。如果界面模块不想显示进程条,只需要简单的传一个空值。
使用:
算法模块接收到IProgress接口后,理论上是可以控制程序进度指示了。不过直接使用IProgress接口很不方便。例如:每次调用IProgress接口之前都需要都要判断一下传入的接口指针是否为空;要实现均匀的进度指示,子函数也必须处理进度条,然而光把IProgress接口传给子函数是不够的,必须传入其它的一些控制参数;最后,我们还要考虑一些必要的优化。所有这些如果直接使用IProgress接口来编码,那种代码会让人觉得很恐怖!
为了方便使用IProgress接口,我们需要一个包装类(假定类名是CProgress)。假定我们有一个名为myfun的暴露给外部模块的函数,它调用了child函数,child是一个简单的循环,占用myfun的70%的时间,myfun自己也有一个循环,占用30%的时间。我们希望能够象下面一样编码:
extern “c” void __declspec(dllexport) myfun(IProgress *Iprg);
void child(CProgress &prg){
for(int i=0; i<10000; ++i){
//do some thing
prg += 0.001;
}
}
void myfun(IProgress *Iprg){
CProgress prg(Iprg);
child( prg(70) );
for(int i=0; i<100; ++i){
//do some thing
prg += 0.03;
}
}
包装:
为了支持类似上面的编码,我们的包装类CProgress至少应该提供下面的公有方法:
class CProgress{
public:
CProgress(IProgress *i);
CProgress(CProgress &);
~CProgress();
void operator += (float r);
void operator =(const char* str);
bool operator!()const;
CProgress operator()(float r);
};
前面三个函数用来构造和析构CProgress对象,接下来三个对应IProgress接口的方法。最后一个函数用来生成子函数需要的进程条对象。CProgress对象应该做下面三个事情:
一:CProgress处理IProgress是否为空这种琐碎的事情。
二:当子函数接受到一个CProgress对象,它不因该考虑自己在主函数中所占的百分比。在它的眼光里,它完全跑完整个CProgress对象。
三:当某个CProgress +=或者()的调用会使得对这个CProgress的进度指示超过100%的时候,CProgress应该截断需要的前进幅度,保证进度指示永远运行在100%范围内。当函数结束的时候,如果进度指示不足100%, CProgress对象的析构函数因该补足需要的进度指示
四:为了不在程序仅仅运行了0.0001%的时候都去调用一下IProgress的+=函数(那样太浪费CPU了)。我们需要CProgress在对IProgress的进度指示前进积累到1%的时候才调用一下IProgress的+=函数。
所有的具体实现请参照原码。下面是给出的实现的原码。最后一个CWinProgress提供一个在主窗口显示程序运行进度的IProgress实现。
// IProgress.h: interface for the IProgress class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_IPROGRESS_H__1E7FCB82_D541_48DE_8A83_D3E934A23082__INCLUDED_)
#define AFX_IPROGRESS_H__1E7FCB82_D541_48DE_8A83_D3E934A23082__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
const float progress_whole = 100;
file://显示程序运行进度的接口
class IProgress
{
public:
file://表示程序前进的进度,整个程序的进度是progress_whole
virtual void operator +=(float)=0;
file://设置当前程序正在干什么的信息
virtual void operator =(const char*)=0;
file://是否取消
virtual bool operator !()const=0;
};
file://这个类包装IProgress方便使用
class CProgress
{
file://这个对象类包装IProgress,实现累计优化
struct proxy{
IProgress* _i;
float _cur,_pre;
proxy(IProgress* i=0):_i(i),_cur(0),_pre(0){}
static int _int(float pre){
return (int)((pre*100)/progress_whole);
}
void operator += (float r){
if( !this || !_i || _int(_pre)==_int(_cur+=r) )
return; file://如果累计不足1%,不递交到IProgress的+=
*_i += _cur-_pre;
_pre = _cur;
}
};
proxy _proxy,*_i;
float _coe,_run;
void operator = (CProgress &p){
_coe = p._coe;
_run = p._run;
_i = p._i;
p._i = 0; file://为了不让p析构的时候重复补足
}
CProgress(proxy *i,float coe)
:_i(i),_coe(coe),_run(0){
}
public:
CProgress(IProgress *i)
:_proxy(i),_coe(1),_run(0){
_i = i? &_proxy:0;
}
CProgress(CProgress &p){
operator = (p);
}
~CProgress(){
*_i += _coe*(progress_whole-_run); file://补足未跑完的部分
}
file://构造子函数的运行指示器
CProgress operator () (float r){
if( _run+r>progress_whole )
r = progress_whole-_run;
_run += r;
return CProgress(_i,_coe*r/progress_whole);
}
file://设置程序运行进度
void operator += (float r){
if( !_i )
return;
if( _run+r>progress_whole )
r = progress_whole-_run;
_run += r;
*_i += r*_coe;
}
file://设置程序运行信息
void operator = (const char *info){
if(_i && _i->_i)
*(_i->_i) = info;
}
file://是否取消
bool operator !()const{
return (_i && _i->_i) ? !*(_i->_i):false;
}
};
#ifdef __AFXWIN_H__
class CWinProgress :public IProgress
{
CString _old;
CString _info;
float _run;
CWnd *_pWnd;
public:
CWinProgress(CWnd *pWnd=AfxGetMainWnd()):
_pWnd(pWnd),_run(0){
if(_pWnd)
_pWnd->GetWindowText(_old);
}
~CWinProgress(){
if(_pWnd)
_pWnd->SetWindowText(_old);
}
void operator = (const char *info){
_info = info;
}
bool operator !()const{
return false;
}
void operator +=(float r){
CString str;
str.Format("%d%% ",(int)(100*(_run+=r)/progress_whole) );
if(_pWnd)
_pWnd->SetWindowText(str+_info);
}
};
#endif
#endif // !defined(AFX_IPROGRESS_H__1E7FCB82_D541_48DE_8A83_D3E934A23082__INCLUDED_)