明辉站/技术开发/内容

完成设置过程提示对话框

技术开发2023-08-11 阅读
[摘要]---- 在使用Windows95 进行文件拷贝或者删除操作时,您一定见到过那种具有飞 文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当 中随时取消操作,而且也使文件拷贝或者删除操作变得生动活泼。其实,在使 用Visual C++ 进行应用程序设计时,我们也可以使用下述方法在...
---- 在使用Windows95 进行文件拷贝或者删除操作时,您一定见到过那种具有飞
文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当
中随时取消操作,而且也使文件拷贝或者删除操作变得生动活泼。其实,在使
用Visual C++ 进行应用程序设计时,我们也可以使用下述方法在适当位置加入自
己的操作过程提示对话框。
为每一个操作过程提示对话框创建一个对话框类。为了下面叙述方便,我们
只假设应用程序需要一个操作过程提示对话框并以“CModel”作为对应的对
话框类的名字。

使用Visual C++ 提供的资源编辑器编辑提示对话框,比如加入一些文字说明
和动画等。

在CModel 类的头文件(Model.h) 中,加入两个成员变量,
CWnd* m_pParent; // 指向调用该提示对话框的框架类( 或对话框类),即它
的“父类”int m_nID;// 记录该提示对话框的ID 号
以及下面两个成员函数:
CModel(CWnd* pParent = NULL); // 舍弃原有的构造函数,或者把原函数
修改成这种无模式对话框的构造函数
BOOL Create(); // 该函数将调用创建基类的Create() 函数创建对话框
在Model.cpp 文件中,加入相应函数的实现部分。
CModel::CModel(CWnd* pParent /*=NULL*/)
: CDialog(CModel::IDD, pParent)
{
m_pParent=pParent;
m_nID=CModel::IDD;
//{{AFX_DATA_INIT(CModel)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}

BOOL CModel::Create()
{
return CDialog::Create(m_nID,m_pParent);
}
同时按下Ctrl 和W 键或直接单击工具条上的ClassWizard 按钮,打
开ClassWizard 对话框。在类名(Class name) 列表框中选择该提示对话框类,
在Object IDs 列表框中选择该类的类名后,在消息(Messages) 列表框中选
择PostNcDestroy 消息并双击它,这时ClassWizard 就会在该对话框类中加入
一个PostNcDestroy() 函数。该函数将会在对话框窗口消失后,
由OnNcDestroy() 函数调用。因此,可以在该函数中加入一些扫尾工作,例如
数据传送,释放指针空间等。
void CModel::PostNcDestroy()
{
// TODO: Add your specialized code here and/or call the base class
delete this;
CDialog::PostNcDestroy();
}
在要调用提示对话框类的类的头文件中,先包含(#include)CModel 类的头文
件,再声明一个指向CModel 类的对象的指针,如m_Dlg,并在该类的构造函数
中,加入“m_Dlg = NULL;”一句。然后,在打开和关闭提示对话框的函数中加
入如下一段程序:
if (m_Dlg==NULL) {//如果当前没用提示对话框在活动,就创建一个
m_Dlg = new CModel(this);
m_Dlg->Create();
GetDlgItem(IDC_EXPORT)->EnableWindow(FALSE);
}
else//否则就激活它
m_Dlg->SetActiveWindow();
另外,再在要关闭提示对话框的地方,加入如下语句:
m_Dlg->DestroyWindow();
m_Dlg=NULL;
---- 至此,您已经拥有了自己的过程操作提示对话框。不过,它还不具有动画和
随时取消操作的功能。您不妨尝试着加入这些功能。另外,笔者也曾尝试过用下面
介绍的方法实现过程操作提示对话框。两种方法比较,可谓各有千秋。如果您希望
上面设计的过程提示对话框能够被多个应用程序共享,那么最好把提示对话框作
为独立的进程来调用。但是,当您还希望在提示对话框与调用者之间传输数据的
话,似乎这一部分介绍的实现方法更简洁且更有效。
应用进程实现对其他应用程序的调用
---- 在我们设计的应用程序中,很可能会用到其他应用程序来完成某一特定功
能。例如,当我们为了便于数据的传输而对诸多文件进行压缩和解压缩时,一种作
法是我们自己设计一个这样的压缩/ 解压缩程序,然后以动态链接库(DLL) 或者函
数库的形式由主应用程序调用。但更方便而且高效的作法是利用现有的这方面的
优秀软件,比如ARJ.EXE,并以进程的形式调用它,再在适当时候关闭它。下面将以
上面所述为例,具体介绍后一种方法的实现过程。
在需要调用ARJ.EXE 进行压缩/ 解压缩的类中,创建一个成员函数,不妨称
作CreateBat(),其作用是生成一个批处理文件。由该批处理文件调
用ARJ.EXE,并给出具体压缩/ 解压缩参数。之后,再利用MS-DOS 的DIR 命令生
成一个临时文件,以作为压缩/ 解压缩工作完成的标志。
void CMyCompress:: CreateBat(CString BatPath,CString ArjPath,
CString BatName,CString ArjFileName,
CString TempPath,CString ExitFlag,BOOL out)
{
LPTSTR lpBuffer;
UINT uSize;
HANDLE hHeap;
uSize=(GetCurrentDirectory(0,NULL))*sizeof(TCHAR);
hHeap=GetProcessHeap();
lpBuffer=(LPSTR)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,uSize);
GetCurrentDirectory(uSize,lpBuffer);
//得知当前目录信息,以便根据需要变换目录
if (lpBuffer!=BatPath) //diferent dir
SetCurrentDirectory(BatPath);
CStdioFile f;
CFileException e;
if (!f.Open( BatName, CFile::modeCreate CFile::modeWrite, &e))
//以BatName的内容创建一个批处理文件
{
AfxMessageBox("不能创建文件"+BatName);
return ;
}
char density[6];
sprintf(density,"%d",mTotalBytes);
---- //mTotalBytes 是由其他函数设定的变量,用于记录用于拷入或拷出文件
的磁盘所具有的最大可用空间
CString Density=density;
CString string;
if (out)//说明是生成做压缩工作的批处理文件
string="arj a -v"+Density;
else //说明是生成做解压缩工作的批处理文件
string="arj e -v"+Density;

string+=" ..\\"+ArjPath+"\\"+ArjFileName+" ";
if (out)
string=string+"..\\"+TempPath+"\\*.* -y -jm\n";
else
string=string+"..\\"+TempPath+"\\ -y -jm\n";

f.WriteString(string);
string="dir >"+ExitFlag+"\n";
f.WriteString(string);
f.Close();
SetCurrentDirectory(lpBuffer);//回复到原来的目录下
}
---- 该函数执行后,将生成一个批处理文件,内容大致是:
---- ARJ A -V1440 压缩后文件的路径名+ 文件名被压缩文件的路径名+ 文件名
-Y -JM
---- DIR > 临时文件名
---- 或者是:
---- ARJ E -V1440 被解压缩文件的路径名+ 文件名解压缩后文件的路径名+ 文
件名-Y -JM
---- DIR > 临时文件名
在需要调用ARJ.EXE 进行压缩/ 解压缩的类中,再创建一个成员函数,不妨称
作RunBat(),其作用是创建和执行进程来运行上述所生成的批处理文件,并
在适当时候撤消进程。
void CMyCompress::RunBat(CString
BatPath,CString fileName,CString ExitFlag)
{
CString lpApplicationName=BatPath+"\\"+fileName;
// 进程执行的应用程序的完全路径名
STARTUPINFO StartupInfo;// 创建进程所需的信息结构变量
GetStartupInfo(&StartupInfo);
StartupInfo.lpReserved=NULL;
StartupInfo.lpDesktop=NULL;
StartupInfo.lpTitle=NULL;
StartupInfo.dwX=0;
StartupInfo.dwY=0;
StartupInfo.dwXSize=200;
StartupInfo.dwYSize=300;
StartupInfo.dwXCountChars=500;
StartupInfo.dwYCountChars=500;
StartupInfo.dwFlags=STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow=SW_HIDE;
// 说明进程将以隐藏的方式在后台执行
StartupInfo.cbReserved2=0;
StartupInfo.lpReserved2=NULL;
StartupInfo.hStdInput=stdin;
StartupInfo.hStdOutput=stdout;
StartupInfo.hStdError=stderr;
LPTSTR lpBuffer;
UINT uSize;
HANDLE hHeap;

uSize=(GetCurrentDirectory(0,NULL))*sizeof(TCHAR);
hHeap=GetProcessHeap();
lpBuffer=(LPSTR)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,uSize);

GetCurrentDirectory(uSize,lpBuffer);
// 得知当前目录信息,以便根据需要变换目录
if (lpBuffer!=BatPath) //diferent dir
SetCurrentDirectory(BatPath);
// 创建进程
if (CreateProcess(lpApplicationName,NULL,NULL,
NULL,FALSE,CREATE_DEFAULT_ERROR_MODE,
NULL,NULL,&StartupInfo,&pro_info))
{
MSG Message;
DeleteFile(ExitFlag);
SetTimer(1,100,NULL);// 设置计时器
Search=TRUE;
while(Search) {
if (::PeekMessage(&Message,NULL,0,0,PM_REMOVE)) {
::TranslateMessage(&Message);
::DispatchMessage(&Message);
}
}
// 进程结束前后的处理工作
DWORDExitCode;
if (!GetExitCodeProcess(pro_info.hProcess,&ExitCode))
AfxMessageBox("GetExitCodeProcess is Failed!");
if (!TerminateProcess(pro_info.hProcess,(UINT)ExitCode))
// 终止进程
AfxMessageBox("TerminateProcess is Failed!");
if (!CloseHandle(pro_info.hProcess))
// 释放被终止进程的句柄
AfxMessageBox("CloseHandle is Failed!");
KillTimer(1);// 撤消计时器
}
else AfxMessageBox("Process Is Not Created!");
SetCurrentDirectory(lpBuffer);// 回复到原来的目录下
}
同时按下Ctrl 和W 键或直接单击工具条上的ClassWizard 按钮,打
开ClassWizard 对话框。在类名(Class name) 列表框中选择需要调用ARJ.EXE
进行压缩/ 解压缩的类,在Object IDs 列表框中选择该类的类名后,在消
息(Messages) 列表框中选择WM_TIMER 消息并双击它,这时ClassWizard 就会
在该类中加入一个OnTimer() 函数。该函数将以一定的时间间隔检查压缩/ 解
压缩程序是否已经执行完毕,即检查作为标志的临时文件是否已经存在,并
及时修改状态变量“Search”,以便通知RunBat() 函数结束进程。
void CMyCompress::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
CFile file;
CFileException Error;
if (file.Open(ExitFlag,CFile::modeRead,&Error)) {
Search=FALSE;
file.Close();
}
}
自编删除目录及其下属文件的函数
---- 高版本的MS-DOS 和Windows 95 都提供了一个可以删除一个或多个目录及其
下属文件和目录的命令,即DeleteTree 命令。然而,无论在MFC 类库还是在Win32 函
数库中,都没有相应的函数与之对应。这样,当我们在自己设计的应用程序中需要
用到DeleteTree 的功能时,自然想到的方法是通过进程调用或者系统调用的方
式( 正如上面部分所述的那样) 调用MD-DOS 或Windows 95 下的DeleteTree 命令。然
而,Win32 函数库已经为我们提供了多种用于文件和目录操作的函数,利用它们不
难设计出自己的DeleteTree() 函数。
---- 读者读到这里,也许会感到有些疑惑,为什么第六部分强调进程调用优于自
我设计的函数,而这一部分又反了过来?是的,在通常情况下,调用应用程序内部
的函数比使用进程或者调用外部函数更灵活并且可以提高执行效率,也便于修
改。所以,象DeleteTree() 这样的功能,利用现有的函数并不难实现,自然就最好
通过内部函数的方式来完成。然而,象设计一个压缩/ 解压缩这样的函数的工作
量,并不比通过进程调用来使用现成品的开销更合算,因为它至少需要我们了解
压缩/ 解压缩的复杂算法,而且调试和维护它也需要一定代价。于是,这个时候,还
是采用“拿来主义”为好。
---- 下面,给出我自己设计的DeleteTree() 函数,仅供参考。
BOOL DeleteTree(CString DirName)
{ //成功:返回TRUE;否则,返回FALSE
BOOL Result;
Result=PreRemoveDirectory(DirName)
&& RemoveDirectory(DirName);
return Result;
}
BOOL PreRemoveDirectory(CString DirName)
{//成功:返回TRUE;否则,返回FALSE
LPTSTR lpBuffer;
UINT uSize;
CString fileName;
HANDLE hHeap;
BOOL result;
HANDLE hFindFile;
WIN32_FIND_DATA FindFileData;
uSize=(GetCurrentDirectory(0,NULL))*sizeof(TCHAR);
hHeap=GetProcessHeap();
lpBuffer=(LPSTR)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,uSize);
GetCurrentDirectory(uSize,lpBuffer);
if (lpBuffer!=DirName) {//调整当前目录
SetCurrentDirectory(DirName);
}
hFindFile=FindFirstFile("*.*",&FindFileData);
CString tFile;
if (hFindFile!=INVALID_HANDLE_VALUE) {
do {
tFile=FindFileData.cFileName;
if ((tFile==".") (tFile=="..")) continue;
if (FindFileData.dwFileAttributes==
FILE_ATTRIBUTE_DIRECTORY){
if (DirName[DirName.GetLength()-1]!='\\')
PreRemoveDirectory(DirName+'\\'+tFile);
else
PreRemoveDirectory(DirName+tFile);
if (!RemoveDirectory(tFile))
result=FALSE;
else
result=TRUE;
}
else
if (!DeleteFile(tFile)) result=FALSE;
else result=TRUE;
}
while (FindNextFile(hFindFile,&FindFileData));
FindClose(hFindFile);
}
else {
SetCurrentDirectory(lpBuffer);
return FALSE;
}
SetCurrentDirectory(lpBuffer); //回复到原来的目录下
return result;
}
如何得到并修改各驱动器的信息
---- 在设计和文件输入/ 输出有关的应用程序时,我们很可能在输入/ 输出文件
前,需要了解一下源驱动器或者目标驱动器的各项信息,比如是否有磁盘在软驱
中,它是否已打开写保护,以及现有磁盘的容量等。遗憾的是,MFC 类库中没有提供
支持这些功能的类,所以我们只能通过Win32 提供的函数来完成我们的要求。下
面,我根据自己的编程实践,通过几段程序,来说明如何利用Win32 提供的函数实
现对驱动器的操作。读者可以根据自己的需要,把介绍的函数稍加修改后,即可插
入到自己设计的应用程序中去。
下面程序的功能是搜索计算机中所有驱动器,选择出其中软盘驱动器的驱动
器号,依次加入到一个下拉列表框中。
void FindDriverInfo()
{
CComboBox* Driver=(CComboBox*)GetDlgItem(IDC_DRIVER);
DWORD dwNumBytesForDriveStrings;
HANDLE hHeap;
LPSTR lp;
CString strLogdrive;
int nNumDrives=0, nDriveNum;
dwNumBytesForDriveStrings=GetLogicalDriveStrings(0,NULL)
*sizeof(TCHAR);//实际存储驱动器号的字符串长度
if (dwNumBytesForDriveStrings!=0) {
hHeap=GetProcessHeap();
lp=(LPSTR)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,
dwNumBytesForDriveStrings);//
GetLogicalDriveStrings(HeapSize(hHeap,0,lp),lp);
StringBox.SetSize(dwNumBytesForDriveStrings/sizeof(TCHAR)+1);
while (*lp!=0) {
if (GetDriveType(lp)==DRIVE_REMOVABLE){
Driver->AddString(lp);
StringBox[nNumDrives]=lp;
nNumDrives++;
}
lp=_tcschr(lp,0)+1;
}
}
else AfxMessageBox("Can't Use The Function GetLogicalDriveStrings!");
}
下面介绍的EmptyDiskSpace() 函数主要负责清空指定驱动器中的磁盘,同时
它还负责记录指定驱动器中磁盘的容量,并得到该磁盘的序列号。在该函数
中,还将调用第七部分提到的PreRemoveDirectory() 函数,来完成清空工作。

BOOL EmptyDiskSpace(CString Driver)
{
BOOL result=TRUE;
DWORDSectorsPerCluster; // address of sectors per cluster
DWORDBytesPerSector; // address of bytes per sector
DWORDNumberOfFreeClusters; // address of number of free clusters
DWORDTotalNumberOfClusters;
DWORDTotalBytes;
DWORDFreeBytes;
int bContinue=1;
char DiskVolumeSerialNumber[30];
//存储驱动器内当前磁盘的序列号
LPCTSTRlpRootPathName;
// address of root directory of the file system
LPTSTRlpVolumeNameBuffer=new char[12];
// address of name of the volume
DWORDnVolumeNameSize=12;
// length of lpVolumeNameBuffer
DWORD VolumeSerialNumber;
// address of volume serial number
DWORD MaximumComponentLength;
// address of system's maximum filename length
DWORD FileSystemFlags;
// address of file system flags
LPTSTRlpFileSystemNameBuffer=new char[10];
// address of name of file system
DWORDnFileSystemNameSize=10;
// length of lpFileSystemNameBuffer
lpRootPathName=Driver;
while (1){
if (GetDiskFreeSpace(Driver, &SectorsPerCluster,
&BytesPerSector, &NumberOfFreeClusters,
&TotalNumberOfClusters))
{//驱动器中有磁盘
TotalBytes=SectorsPerCluster*BytesPerSector
*TotalNumberOfClusters;//磁盘总容量
FreeBytes=SectorsPerCluster*BytesPerSector
*NumberOfFreeClusters;//磁盘空闲空间容量
GetVolumeInformation(lpRootPathName,
lpVolumeNameBuffer, nVolumeNameSize,
&VolumeSerialNumber,
&MaximumComponentLength,
&FileSystemFlags,
lpFileSystemNameBuffer, nFileSystemNameSize);
sprintf(DiskVolumeSerialNumber,"%X",VolumeSerialNumber);
//得到驱动器内当前磁盘的序列号
SetmTotalBytes(TotalBytes/1024);//存储指定驱动器中磁盘的容量
if (TotalBytes!=FreeBytes){//当磁盘总容量不等于空闲空间容量时,
应该执行清空操作
while (bContinue) {
if ((bContinue==2) (MessageBox
("在驱动器"+m_Driver+"中的磁盘尚存有数据.
\n您愿意让系统为您删除它们吗?",
"提问",MB_YESNO MB_ICONQUESTION)==IDYES))
if (!PreRemoveDirectory(Driver))//无法执行清空操作
if (MessageBox("因某种原因系统无法删除
在驱动器"+m_Driver+"中的磁盘上的数据.
\n请检查磁盘是否没有关闭写保护.
\n您愿意再试一次吗?",
"问题",MB_YESNO MB_ICONERROR)==IDYES) {
bContinue=2;
continue;
}
else {
bContinue=0;
result=FALSE;
}
else {
MessageBox("成功删除磁盘上的数据!",
"提示信息",MB_OK MB_ICONINFORMATION);
bContinue=0;
result=TRUE;
}
else {//THE FIRST IF'S ELSE
bContinue=0;
result=FALSE;
}
}
}
else result=TRUE;
break;
}
else {
if (MessageBox("没有磁盘在驱动器"+m_Driver+"中.
\n您愿意插入一张磁盘再来一次吗?",
"问题",MB_YESNO MB_ICONASTERISK)==IDYES) continue;
else break;
}
}//END OF WHILE
return result;
}
在MS-DOS 和Windows95 中,磁盘卷标最多由11 个字符组成,并且字母的大小写
不加区分。当需要设定指定驱动器中磁盘的卷标时,只要调用Win32
的SetVolumeLabel() 函数即可,并在第一个参数中指明磁盘所在的驱动器
号,在第二个参数中指明新的卷标号。例如,SetVolumeLabel(DriverNum,
NewVolumeLabel)。 

……

相关阅读