Visual C++ 5.0编程经验(下)

发表于:2007-07-04来源:作者:点击数: 标签:
北京大学计算机系 蒋志华 实现操作过程提示对话框的一种方法 在使用 Windows 95进行文件拷贝或者删除操作时,您一定见到过那种具有飞文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当中随时取消操作,而且也使文件拷贝或者删除操作变
北京大学计算机系 蒋志华

实现操作过程提示对话框的一种方法

  在使用Windows95进行文件拷贝或者删除操作时,您一定见到过那种具有飞文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当中随时取消操作,而且也使文件拷贝或者删除操作变得生动活泼。其实,在使用VisualC++进行应用程序设计时,我们也可以使用下述方法在适当位置加入自己的操作过程提示对话框。 为每一个操作过程提示对话框创建一个对话框类。为了下面叙述方便,我们只假设应用程序需要一个操作过程提示对话框并以“CModel”作为对应的对话框类的名字。
使用VisualC++提供的资源编辑器编辑提示对话框,比如加入一些文字说明和动画等。
在CModel类的头文件(Model.h)中,加入两个成员变量,
CWnd*m_pParent;//指向调用该提示对话框的框架类(或对话框类),即它的“父类”intm_nID;//记录该提示对话框的ID号
以及下面两个成员函数:
CModel(CWnd*pParent=NULL);//舍弃原有的构造函数,或者把原函数修改成这种无模式对话框的构造函数
BOOLCreate();//该函数将调用创建基类的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)列表框中选择该提示对话框类,在ObjectIDs列表框中选择该类的类名后,在消息(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进行压缩/解压缩的类,在ObjectIDs列表框中选择该类的类名后,在消息(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和Windows95都提供了一个可以删除一个或多个目录及其下属文件和目录的命令,即DeleteTree命令。然而,无论在MFC类库还是在Win32函数库中,都没有相应的函数与之对应。这样,当我们在自己设计的应用程序中需要用到DeleteTree的功能时,自然想到的方法是通过进程调用或者系统调用的方式(正如上面部分所述的那样)调用MD-DOS或Windows95下的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)。

back.gif (1185 字节)

原文转自:http://www.ltesting.net