在使用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)。