北京大学计算机系 蒋志华
在使用Windows95进行文件拷贝或者删除操作时,您一定见到过那种具有飞文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当中随时取消操作,而且也使文件拷贝或者删除操作变得生动活泼。其实,在使用VisualC++进行应用程序设计时,我们也可以使用下述方法在适当位置加入自己的操作过程提示对话框。
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);
}
void CModel::PostNcDestroy()
{
// TODO: Add your specialized code here and/or call the base class
delete this;
CDialog::PostNcDestroy();
}
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,并以进程的形式调用它,再在适当时候关闭它。下面将以上面所述为例,具体介绍后一种方法的实现过程。
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>临时文件名
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);// 回 复 到 原 来 的 目 录 下
}
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!");
}
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;
}