当前位置导航:炫浪网>>网络学院>>编程开发>>C++教程>>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)。

相关内容
赞助商链接