天
天 剑
另一种改变窗口标题的方法
在1997年9月1日出版的《计算机世界》上有一篇名为《VisualC++4.0编程经验谈》的 文章,其中提到过一种改变窗口标题的方法,即在应用程序的框架类CMainFrame类(注:它是CFrameWnd类的派生类) 的成员函数PreCreateWindow( )中修改CFrameWnd类的成员变量m_strTitle 的值。这种方法的不足之处就是只能一次性的设置窗口标题的内容,不能在程序运行过程中随时修改它。 比如当需要把鼠标的当前坐标在窗口标题上显示时,我们就要使用到VisualC++提供的全程函数BOOL SetWindowText(HWND hWnd,LPCTSTR lpString)。这个函数实际上是一个Win32函数。它 的第一个参数要求是一个窗口框架的句柄,而第二个参 数要求是一个指向一个常量字符串的32位指针,即LPCTSTR 类型的变量。通常情况下,我们往往会在应用程序的 文档类或视类中动态改变窗口标题的内容,从而没有 现成的指向窗口框架的句柄供我们使用,因此我们还 需要在使用SetWindowText()函数前先调用另一个Win32函数AfxGetMainWnd(), 来获得一个指向应用程序的框架类的指针,
例如使用语句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式调用SetWindowText()函数:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一个CString类的变量。
--------------------------------------------------------------------------------
如何把多于256色的位图作为资源加入到应用程序中
曾经使用过VisualC++进行应用程序 开发的同行们都知道,VisualC++5.0以前版本中自带的位 图编辑器不能浏览和编辑256色以上的位图,并且资源 文件中也不允许嵌入(Import)256色以上的位图作为资源(否则,在应用程序运行时 会报错)。这一特性使得我们 用VisualC++开发应用程序时不得不使用其他方法来增强 界面图画的美观性,于是就有使用VisualC++开发应用程 序内核,用VisualBasic开发界面部分的组合方法。好在Visual C++5.0中这个问题有了改善。首先,位图编辑器可以创 建并编辑256色的位图了。另外,VisualC++5.0允许程序员把256 色以上的位图嵌入到资源中,尽管仍然无法在VisualC++ 的位图编辑器中浏览,并且还要求必须选择Win32Release 作为编译方式生成可执行的应用程序。另外一个限制 条件是作为资源的256色以上的位图不能由应用程序内 核自动打开和关闭。比如说,在上述那篇名为 《VisualC++ 4.0编程经验谈》的文章中曾经提到过一种为对话框加 入位图式按钮的方法,即由程序开发者为每个按钮创 建四幅位图,分别用于表示按钮的弹起状态(UP)、按下 状态(DOWN)、输入焦点状态(FOCUS)和禁止状态(DISABLE),并且 必须以该按钮的标题名与上述四种状态之一的组合 作为位图的标识,以便应用程序在绘制位图按钮时, 可以自动地找到相应的资源(即位图)。然而这一自动映射只限制于 VisualC++位图编辑器能够打开的位图。因 此如果选择256色以上的位图作为位图按钮的资源,并 也希望达到上述四状态的相互切换的话,就必须用到 下述的函数和程序设计参考模型。
设置四个常量,BUTTON_UP,BUTTON_DOWN,BUTTON_FOCUS,BUTTON_DISABLE,分 别用于标识各按钮的当前状态。
在应用程序的相应对话框类中为每个位图按钮(为下 面叙述方便,不妨假设为两个)设置一个CRect类的对 象,m_rect1和m_rect2,来记载各按钮在对话框中所占据的 坐标矩形。再在该对话框类中为每个按钮设置一个整 型变量,Buton1_Status,Button2_Status,记录各按钮的当前状态。 然后在对话框的构造函数中初始化这些CRect类的对象 和整型变量。
在该对话框类中创建分别响应鼠标各种状态的消息 处理函数,如OnMouseMove(),OnLButtonUp(),OnLButtonDown()等。
同时按下Ctrl和W键或直接单击工具条上的ClassWizard按 钮,打开ClassWizard对话框。在类名(Classname)列表框中选择 对话框类,在ObjectIDs列表框中选择该类的类名后,在 消息(Messages)列表框中选择WM_PAINT消息并双击它,这时ClassWizard 就会在该对话框类中加入一个OnPaint()函数。该函数将 在我们的程序中起重要作用。之后,再在ObjectIDs列表 框中选择新加入的工具条按钮的标识名,双击消息列 表框中的COMMAND消息,ClassWizard又会在对话框类中加入相 应的消息处理函数。关闭ClassWizard对话框。
下面给出部分程序代码,仅供参考。
void CTESTDlg::OnPaint()
{
CWnd* pWnd;
CDC* pDC;
CDC* pDisplayMemDC;
CBitmap* pBitmap;
pWnd=GetDlgItem(IDC_IMAGE1);//得到指向第一个位图按钮的指针
pDC=pWnd->GetDC();//获得一个窗口设备用于画图
pWnd->Invalidate();//使窗口无效,从而更新它
pWnd->UpdateWindow();
pDisplayMemDC=new CDC;
pBitmap=new CBitmap;
pDisplayMemDC->CreateCompatibleDC(pDC);
if (Change1)
{
//说明第一个按钮的状态发生了变化
switch (Button1_Status){
case BUTTON_DISABLE:
pBitmap->LoadBitmap(IMAGE1_DISABLE);//装入位图
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
//把位图拷贝到指定区域
break;
case BUTTON_UP:
pBitmap->LoadBitmap(IMAGE1_UP);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC, 0,0,SRCCOPY);
break;
case BUTTON_FOCUS:
pBitmap->LoadBitmap(IMAGE1_FOCUS);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
case BUTTON_DOWN:
pBitmap->LoadBitmap(IMAGE1_DOWN);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
}
}
delete pDisplayMemDC;
delete pBitmap;
pWnd=GetDlgItem(IDC_IMAGE2);
pDC=pWnd->GetDC();
pWnd->Invalidate();
pWnd->UpdateWindow();
pDisplayMemDC=new CDC;
pBitmap=new CBitmap;
pDisplayMemDC->CreateCompatibleDC(pDC);
if (Change2)
{
//说明第二个按钮的状态发生了变化
switch (Button2_Status){
case BUTTON_DISABLE:
pBitmap->LoadBitmap(IMAGE2_DISABLE);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
case BUTTON_UP:
pBitmap->LoadBitmap(IMAGE2_UP);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
case BUTTON_FOCUS:
pBitmap->LoadBitmap(IMAGE2_FOCUS);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
case BUTTON_DOWN:
pBitmap->LoadBitmap(IMAGE2_DOWN);
pDisplayMemDC->SelectObject(pBitmap);
pDC->BitBlt(0,0,140,30,pDisplayMemDC,0,0,SRCCOPY);
break;
}
}
delete pDisplayMemDC;
delete pBitmap;
CDialog::OnPaint();
}
void CTESTDlg::OnMouseMove(UINT nFlags, CPoint point)
{
CRect rect=CRect(0,0,1,1);
CRgn rgn1,rgn2;//记录各位图按钮所占据的矩形区域
rgn1.CreateRectRgnIndirect(m_rect1);
//rgn1记录第一个位图按钮所占据的矩形区域
if (rgn1.PtInRegion(point))
{//鼠标当前是否已落入第一个位图按钮所占据的矩形区域
if ( (Button1_Status!=BUTTON_FOCUS)
&& (Button1_Status!=BUTTON_DISABLE) ) {
//如果位图按钮的当前状态不是输入焦点状态并且也不是禁止状态
Button1_Status= BUTTON_FOCUS;
Change1=true;
InvalidateRect(rect,FALSE);
}
if ( (Button2_Status!=BUTTON_UP) &&
(Button2_Status!=BUTTON_DISABLE) ) {
Button2_Status= BUTTON_UP;
Change2=true;
InvalidateRect(rect,FALSE);
}
}
else{
rgn2.CreateRectRgnIndirect(m_rect2);
if (rgn2.PtInRegion(point)){
if ( (Button2_Status!=BUTTON_FOCUS)
&& (Button2_Status!=BUTTON_DISABLE) ) {
Button2_Status= BUTTON_FOCUS;
Change2=true;
InvalidateRect(rect,FALSE);
}
if ( (Button1_Status!=BUTTON_UP)
&& (Button1_Status!=BUTTON_DISABLE) ) {
Button1_Status= BUTTON_UP;
Change1=true;
InvalidateRect(rect,FALSE);
}
}
}
CDialog::OnMouseMove(nFlags, point);
}
void CTESTDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
CRect rect=CRect(0,0,1,1);
CRgn rgn1,rgn2;
rgn1.CreateRectRgnIndirect(m_rect1);
if (rgn1.PtInRegion(point)){
if ( (Button1_Status!=BUTTON_UP)
&& (Button1_Status!=BUTTON_DISABLE) ) {
Button1_Status=BUTTON_UP;
Change1=true;
InvalidateRect(rect,FALSE);
}
}
else{
rgn2.CreateRectRgnIndirect(m_rect2);
if (rgn2.PtInRegion(point)){
if ( (Button2_Status!=BUTTON_UP)
&& (Button2_Status!=BUTTON_DISABLE) ) {
Button2_Status=BUTTON_UP;
Change2=true;
InvalidateRect(rect,FALSE);
}
}
}
CDialog::OnLButtonUp(nFlags, point);
}
void CTESTDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
CRect rect=CRect(0,0,1,1);
CRgn rgn1,rgn2;
rgn1.CreateRectRgnIndirect(m_rect1);
if (rgn1.PtInRegion(point)){
if ( (Button1_Status!=BUTTON_DOWN)
&& (Button1_Status!=BUTTON_DISABLE) ) {
Button1_Status=BUTTON_DOWN;
Change1=true;
InvalidateRect(rect,FALSE);
}
if ( (Button2_Status!=BUTTON_UP)
&& (Button2_Status!=BUTTON_DISABLE) ) {
Button2_Status=BUTTON_UP;
Change2=true;
InvalidateRect(rect,FALSE);
}
}
else{
rgn2.CreateRectRgnIndirect(m_rect2);
if (rgn2.PtInRegion(point)){
if ( (Button2_Status!=BUTTON_DOWN)
&& (Button2_Status!=BUTTON_DISABLE) ) {
Button2_Status=BUTTON_DOWN;
Change2=true;
InvalidateRect(rect,FALSE);
}
if ( (Button1_Status!=BUTTON_UP)
&& (Button1_Status!=BUTTON_DISABLE) ) {
Button1_Status=BUTTON_UP;
Change1=true;
InvalidateRect(rect,FALSE);
}
}
}
CDialog::OnLButtonDown(nFlags, point);
}
--------------------------------------------------------------------------------
如何在应用程序中不加载菜单、工具条和状态条
问题的提出:在通过AppWizard生成的具有窗口框架结构的应用程序(SDI和MDI) 中,MFC类库已为我们加载上了菜单(包括一个系统菜单)、工具条和状态条。但有时由于特殊需要,我们可能希望在自己的应用程序中事先不加载菜单、工具条和状 态条。这时就需要我们手动地删除和修改一些类中的语句。
打开MainFrm.cpp文件,使用工具条上的函数下拉列表框 找到OnCreate()函数。按照下述程序注释掉创建工具条和状态条部分的语句。
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
//在此处开始加注释符号
/* if (!m_wndToolBar.Create(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle()
| CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
// TODO: Delete these three lines if you don't want the toolbar to be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
在处结束注释*/
return 0;
}
在MainFrm.cpp文件中,使用工具条上的函数下拉列表框 找到PreCreateWindow( )函数。参照第三部分中的程序,加入特 定的窗口框架属性,不妨把cs.style设置成如下形式,即 不加载系统菜单。
//Createa window without min/max buttons,system menu,or sizable border
cs.style=WS_OVERLAPPED|WS_BORDER;
在主应用程序,即含有定义theApp全程变量的.cpp文件 中,使用工具条上的函数下拉列表框找到InitInstance()函 数。在“pDocTemplate=new CSingleDocTemplate”一句中,用NULL替换IDR_MAINFRAME。 如下段程序所示:
BOOL CYourMainApp::InitInstance()
{
//.....此处略去一部分无关语句
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
NULL, //IDR_MAINFRAME,
//用NULL替换IDR_MAINFRAME
RUNTIME_CLASS(CNoBarDoc),
RUNTIME_CLASS(CMainFrame),
// main SDI frame window
RUNTIME_CLASS(CNoBarView));
AddDocTemplate(pDocTemplate);
//.....此处略去一部分无关语句
}
找到Visual C++ 编辑器的工具条上的编译方式下拉列表框,选择Win32 Release,生成Release版本的应用程序。 至此,我们就得到了不含菜单、工具条和状态条结构的应用程序。
--------------------------------------------------------------------------------
实现操作过程提示对话框的一种方法
在使用Windows95进行文件拷贝或者删除操作时,您一定见到过那种具有飞文件动画的操作过程提示对话框。这一功能的加入不仅使我们能够在操作过程当中随时取消操作,而且也使文件拷贝或者删除操作变得生动活泼。其实,在使用VisualC++进行 应用程序设计时,我们也可以使用下述方法在适当位置加入自己的操作过程提示对 话框。为每一个操作过程提示对话框创建一个对话框类。为了下面叙述方便,我们只假设应用程序需要一个操作过程提示对话框并以“CModel”作为对应的对话框类的名字。使用VisualC++提供的资源编辑器编辑提示对话框,比如加入一些文字说明和动画等。在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)列表框中选择该提示对话框类,在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 >临时文件名或者是:
ARJE-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或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)。