7.6.1 目录监视的基础知识
要监视指定目录中的变化可以使用FindFirstChangeNotification函数。此函数创建一个改变通知对象,设置初始的改变通知过滤条件。在指定的目录或子目录下,当一个符合过滤条件的改变发生时,一个在通知句柄上的等待将会成功(等待函数返回)。函数原型如下。
HANDLE FindFirstChangeNotification(
LPCTSTR lpPathName, // 指定要监视的目录
BOOL bWatchSubtree, // 指定是否监视lpPathName目录下的所有子目录
DWORD dwNotifyFilter // 指定过滤条件
);
dwNotifyFilter参数指定了能够满足改变通知等待(即能够使等待函数返回)的过滤条件,可以是以下值的组合:
FILE_NOTIFY_CHANGE_FILE_NAME 要求监视文件名称的改变
FILE_NOTIFY_CHANGE_DIR_NAME 要求监视目录名称的改变
FILE_NOTIFY_CHANGE_ATTRIBUTES 要求监视属性的改变
FILE_NOTIFY_CHANGE_SIZE 要求监视文件大小的改变
FILE_NOTIFY_CHANGE_LAST_WRITE 要求监视最后写入时间的改变
FILE_NOTIFY_CHANGE_SECURITY 要求监视安全属性的改变
函数执行成功返回值是改变通知对象句柄,INVALID_HANDLE_VALUE是失败后的返回值。返回的句柄可传递给等待函数(如WaitForSingleObject等)。当监视的目录或子目录中一个过滤条件满足的时候,等待就会返回。
等待函数返回之后,应用程序可以处理这个改变,并调用FindNextChangeNotification函数继续监视目录。当不再使用这个句柄时,应当调用FindCloseChangeNotification函数关闭它。这两个函数的惟一参数是FindFirstChangeNotification函数返回的句柄。
7.6.2 实例程序
实例程序07MonitorDir说明了上述函数的具体使用方法,程序界面如图7.16所示。用户监视目录的任何改变都会显示在“改变”窗口。具体可以查看配套光盘的源程序代码。
这个程序创建了一个辅助线程来监视目录。在用户单击开始按钮之后,程序根据用户的选项创建数量不同的改变通知句柄,然后调用AfxBeginThread函数启动辅助线程,并将创建的句柄数组传递给线程函数。辅助线程启动后调用WaitForMultipleObjects函数在多个改变通知句柄上等待,如果有一个期待的改变事件发生,等待函数返回,程序通过返回值判断是哪一个改变通知句柄使函数返回。
程序最多可以创建6个改变通知句柄(因为过滤条件有6个),用句柄数组m_arhChange[6]来保存它们。但是如果用户只想监视复选框所示的一部分,就不能创建6个通知句柄了。为了使用WaitForMultipleObjects函数在m_arhChange[6]数组句柄上等待,可以用一个事件对象句柄来填充不满6个的部分,详细情况请看代码中相关部分。整个程序源代码如下。
MonitorDir.h
// MonitorDir.h文件
#include <afxwin.h>
#include <afxcmn.h> // 为了使用CStatusBarCtrl类
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
};
class CMonitorDialog : public CDialog
{
public:
CMonitorDialog(CWnd* pParentWnd = NULL);
~CMonitorDialog();
protected:
// 向输出窗口添加文本
void AddStringToList(LPCTSTR lpszString);
// 监视线程
friend UINT MonitorThread(LPVOID lpParam);
CStatusBarCtrl m_bar; // 一个状态栏对象
HANDLE m_hEvent; // 用于占位的事件对象句柄
HANDLE m_arhChange[6]; // 改变通知事件的6个句柄
BOOL m_bExit; // 指示监视线程是否退出
protected:
virtual BOOL OnInitDialog();
virtual void OnCancel();
afx_msg void OnBrowser();
afx_msg void OnStart();
afx_msg void OnStop();
afx_msg void OnClear();
DECLARE_MESSAGE_MAP()
};
MonitorDir.cpp
// MonitorDir.cpp文件
#include "resource.h"
#include "MonitorDir.h"
#include "DirDialog.h"
#include "SkinMagicLib.h"
// 注意,如果MFC是动态链接到工程中的,则应该选择SkinMagicLibMD6Trial.lib库
#pragma comment(lib, "SkinMagicLibMT6Trial")
CMyApp theApp;
BOOL CMyApp::InitInstance()
{
//===SkinMagic===
// 初始化SkinMagic库
VERIFY(InitSkinMagicLib(AfxGetInstanceHandle(), "MonitorDir", NULL, NULL));
// 从资源中加载皮肤文件。也可以用代码“LoadSkinFile("corona.smf")”直接从文件中加载
if(LoadSkinFromResource(AfxGetInstanceHandle(), (LPCTSTR)IDR_SKINMAGIC1, "SKINMAGIC"))
{
// 设置对话框默认皮肤
SetDialogSkin("Dialog");
}
//===SkinMagic===
CMonitorDialog dlg;
m_pMainWnd = &dlg;
dlg.DoModal();
return FALSE; // 返回FALSE阻止程序进入消息循环
}
int CMyApp::ExitInstance()
{
//===SkinMagic===
// 释放SkinMagic库申请的内存
ExitSkinMagicLib();
//===SkinMagic===
return CWinApp::ExitInstance();
}
CMonitorDialog::CMonitorDialog(CWnd* pParentWnd):CDialog(IDD_MAINDIALOG, pParentWnd)
{
m_hEvent = ::CreateEvent(NULL, FALSE, 0, NULL);
}
CMonitorDialog::~CMonitorDialog()
{
::CloseHandle(m_hEvent);
}
BEGIN_MESSAGE_MAP(CMonitorDialog, CDialog)
ON_BN_CLICKED(IDC_START, OnStart)
ON_BN_CLICKED(IDC_STOP, OnStop)
ON_BN_CLICKED(IDC_BROWSER, OnBrowser)
ON_BN_CLICKED(IDC_CLEAR, OnClear)
END_MESSAGE_MAP()
BOOL CMonitorDialog::OnInitDialog()
{
// 让父类进行内部初始化
CDialog::OnInitDialog();
// 设置图标
SetIcon(theApp.LoadIcon(IDI_MAIN), FALSE);
// 创建状态栏,设置它的属性(CStatusBarCtrl类封装了对状态栏控件的操作)
m_bar.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP, CRect(0, 0, 0, 0), this, 101);
m_bar.SetBkColor(RGB(0xa6, 0xca, 0xf0)); // 背景色
int arWidth[] = { 250, -1 };
m_bar.SetParts(2, arWidth); // 分栏
m_bar.SetText(" Windows程序设计入门到深入!", 1, 0); // 第二个栏的文本
m_bar.SetText(" 空闲", 0, 0); // 第一个栏的文本
// 无效停止按钮
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
// 设置各个复选框为选中状态
((CButton*)GetDlgItem(IDC_SUBDIR))->SetCheck(1);
((CButton*)GetDlgItem(IDC_FILENAME_CHANGE))->SetCheck(1);
((CButton*)GetDlgItem(IDC_FILESIZE_CHANGE))->SetCheck(1);
((CButton*)GetDlgItem(IDC_DIRNAME_CHANGE))->SetCheck(1);
((CButton*)GetDlgItem(IDC_LASTWRITE_CHANGE))->SetCheck(1);
((CButton*)GetDlgItem(IDC_ATTRIBUTE_CHANGE))->SetCheck(1);
((CButton*)GetDlgItem(IDC_SECURITY_CHANGE))->SetCheck(1);
return TRUE;
}
void CMonitorDialog::OnBrowser() // 用户点击浏览按钮
{
// 弹出选择目录对话框
CDirDialog dir;
if(dir.DoBrowse(m_hWnd, "请选择您要监视的目录:"))
{
GetDlgItem(IDC_TARGETDIR)->SetWindowText(dir.GetPath());
}
}
void CMonitorDialog::OnStart() // 用户点击开始按钮
{
CString strDir;
// 取得目录名称
GetDlgItem(IDC_TARGETDIR)->GetWindowText(strDir);
if(strDir.IsEmpty())
{
MessageBox("请选择一个要监视的目录!");
return;
}
// 用事件对象句柄初始化句柄数组
for(int i=0; i<6; i++)
m_arhChange[i] = m_hEvent;
m_bExit = FALSE;
// 是否要监视子目录
BOOL bSubDir = ((CButton*)GetDlgItem(IDC_SUBDIR))->GetCheck();
BOOL bNeedExecute = FALSE;
// 监视目录名称的改变 arhChange[0]
if(((CButton*)GetDlgItem(IDC_DIRNAME_CHANGE))->GetCheck())
{
m_arhChange[0] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_DIR_NAME);
bNeedExecute = TRUE;
}
// 监视文件名称的改变 arhChange[1]
if(((CButton*)GetDlgItem(IDC_FILENAME_CHANGE))->GetCheck())
{
m_arhChange[1] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_FILE_NAME);
bNeedExecute = TRUE;
}
// 监视属性的改变 arhChange[2]
if(((CButton*)GetDlgItem(IDC_ATTRIBUTE_CHANGE))->GetCheck())
{
m_arhChange[2] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_ATTRIBUTES);
bNeedExecute = TRUE;
}
// 监视文件大小的改变 arhChange[3]
if(((CButton*)GetDlgItem(IDC_FILESIZE_CHANGE))->GetCheck())
{
m_arhChange[3] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_SIZE);
bNeedExecute = TRUE;
}
// 监视最后写入时间的改变 arhChange[4]
if(((CButton*)GetDlgItem(IDC_LASTWRITE_CHANGE))->GetCheck())
{
m_arhChange[4] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_LAST_WRITE);
bNeedExecute = TRUE;
}
// 监视安全属性的改变 arhChange[5]
if(((CButton*)GetDlgItem(IDC_SECURITY_CHANGE))->GetCheck())
{
m_arhChange[5] =
::FindFirstChangeNotification(strDir, bSubDir, FILE_NOTIFY_CHANGE_SECURITY);
bNeedExecute = TRUE;
}
if(!bNeedExecute)
{
MessageBox("请选择一个监视类型!");
return;
}
// 启动监视线程
AfxBeginThread(MonitorThread, this);
// 更新界面
GetDlgItem(IDC_START)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
m_bar.SetText(" 正在监视...", 0, 0);
}
void CMonitorDialog::OnStop() // 用户点击停止按钮
{
if(!m_bExit)
{
// 设置退出标志
m_bExit = TRUE;
for(int i=0; i<6; i++)
{
if(m_arhChange[i] != m_hEvent)
::FindCloseChangeNotification(m_arhChange[i]);
}
}
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
m_bar.SetText(" 空闲", 0, 0);
}
void CMonitorDialog::OnClear() // 用户点击清空按钮
{
GetDlgItem(IDC_EDITCHANGES)->SetWindowText("");
}
void CMonitorDialog::OnCancel()
{
OnStop();
CDialog::OnCancel();
}
void CMonitorDialog::AddStringToList(LPCTSTR lpszString)
{
// 向"改变"窗口中添加文本
CString strEdit;
GetDlgItem(IDC_EDITCHANGES)->GetWindowText(strEdit);
strEdit += lpszString;
GetDlgItem(IDC_EDITCHANGES)->SetWindowText(strEdit);
}
UINT MonitorThread(LPVOID lpParam)
{
CMonitorDialog* pDlg = (CMonitorDialog*)lpParam;
while(TRUE)
{
// 在多个改变通知事件上等待
DWORD nObjectWait = ::WaitForMultipleObjects(
6, pDlg->m_arhChange, FALSE, INFINITE);
if(pDlg->m_bExit) // 用户要求退出
break;
// 查找促使等待函数返回的句柄,通知用户
int nIndex = nObjectWait - WAIT_OBJECT_0;
switch(nIndex)
{
case 0:
pDlg->AddStringToList(" Directory name changed \r\n");
break;
case 1:
pDlg->AddStringToList(" File name changed \r\n");
break;
case 2:
pDlg->AddStringToList(" File attribute changed \r\n");
break;
case 3:
pDlg->AddStringToList(" File size changed \r\n");
break;
case 4:
pDlg->AddStringToList(" Last write changed \r\n");
break;
case 5:
pDlg->AddStringToList(" Security changed \r\n");
break;
}
// 继续监视
::FindNextChangeNotification(pDlg->m_arhChange[nObjectWait]);
}
return 0;
}
/*
InitSkinMagicLib(AfxGetInstanceHandle(), "MonitorDir", NULL, NULL);
if(LoadSkinFile("Devior.smf"))
{
SetDialogSkin("Dialog");
}
*/
用模式对话框作为程序的为主窗口时,要在InitInstance函数中调用CDialog类的成员函数DoModal以创建对话框,而且一定要返回FALSE来阻止框架程序进入消息循环。
程序在控制子窗口控件时多次用到了CWnd类的GetDlgItem成员函数。此函数的作用是为指定的子窗口创建是一个临时的CWnd对象,其伪实现代码如下。
CWnd* CWnd::GetDlgItem(int nID) const
{
return CWnd::FromHandle(::GetDlgItem(m_hWnd, nID));
}
GetDlgItem取得nID子窗口的窗口句柄,然后调用CWnd::FromHandle函数为这个句柄创建临时的CWnd类对象(如果得到的窗口句柄不在句柄映射中的话),并返回此临时对象的指针。这些临时对象是在线程空闲(消息队列中没有消息)时框架程序自动删除的,所以不要保存函数CWnd::GetDlgItem返回的CWnd指针在其他场合使用。
向“改变”窗口中添加文本的自定义函数CMonitorDialog::AddStringToList使用了CString类。这是一个广泛使用的字符串类,它封装了大部分对字符串的操作。除了提供一般的字符串查找、替换、连接等操作外,这个类通过让多个CString对象共同使用一块内存还为程序节约了内存空间。具体机制是这样的,当用一个CString对象给另外一个CString对象赋值的时候,框架程序仅仅让这两个对象共用一块内存,直到有一个对象要写这块内存,它才分配新的内存。这就是所谓的CopyBeforeWrite了。
7.6.3 使用SkinMagic美化界面
现在许多软件的界面都非常“华丽”,主要是因为它们使用了第三方美化软件。本小节介绍如何使用现在比较流行的SkinMagic库美化程序界面。
SkinMagic Toolkit是一套功能强大的界面解决方案库,它提倡界面和业务逻辑相分离,将程序员从烦琐的界面设计中彻底解放出来,将精力集中在业务功能的实现上,提高产品的开发效率。可以从官方网站http://www.appspeed.com/china/html/download.html
免费下载试用(配套光盘的SkinMagic文件夹下有2.10版本)。
在电脑上安装此工具包(2.10版)之后,会在安装目录创建一个SkinMagic Toolkit 2.21 Trial文件夹,下面所述的文件和文件夹都在这个目录下。编程时要用的是Include文件夹下的SkinMagicLib.h文件、Lib文件夹下的几个.Lib文件和skin文件夹下的.smf文件。这些Lib库文件分别提供了对Visual C++ 6.0和Visual C++ 7.0静态链接和动态链接的支持。.smf是自定义的皮肤文件,它包含了各种界面对象的具体定义,可以使用皮肤编辑器SkinMagicBuilder编辑或者创建它们。以2.10版本为例,美化目录监视器程序的过程如下:
(1)复制需要的文件。将Include文件夹下的SkinMagicLib.h文件、Lib文件夹下的SkinMagicLibMT6Trial.lib文件(假设MFC是静态链接到工程中的,见6.6节)和skin文件夹下corona.smf文件复制到07MonitorDir工程目录下。
(2)添加文件包含。所有的SkinMagic函数原型都定义在SkinMagicLib.h文件中,其实现代码在SkinMagicLibMT6Trial.lib静态库中,所以要在MonitorDir.cpp文件中添加如下代码。
#include "SkinMagicLib.h"
// 注意,如果MFC是动态链接到工程中的,则应该选择SkinMagicLibMD6Trial.lib库
#pragma comment(lib, "SkinMagicLibMT6Trial")
(3)修改程序代码。在CMyApp::InitInstance函数创建主窗口之前,添加如下程序代码。
// 初始化SkinMagic库
VERIFY(InitSkinMagicLib(AfxGetInstanceHandle(), "MonitorDir", NULL, NULL));
// 从资源中加载皮肤文件。也可以用代码“LoadSkinFile("corona.smf")”直接从文件中加载
if(LoadSkinFromResource(AfxGetInstanceHandle(), (LPCTSTR)IDR_SKINMAGIC1, "SKINMAGIC"))
{
// 设置对话框默认皮肤
SetDialogSkin("Dialog");
}
在CMyApp类中重载ExitInstance函数,其实现代码如下。
int CMyApp::ExitInstance()
{
// 释放SkinMagic库申请的内存
ExitSkinMagicLib();
return CWinApp::ExitInstance();
}
(4)修改资源文件。如果在第3步使用了LoadSkinFromResource函数从资源加载皮肤文件,就要将皮肤文件作为自定义资源添加到工程中,具体过程是这样的:单击菜单命令“Insert/Resource…”,弹出插入资源对话框;单击按钮“Import…”,导入皮肤文件corona.smf到工程中;因为这不是标准的资源,所以会弹出自定义资源类型对话框,这里输入“SKINMAGIC”,单击OK按钮即可。
现在重新编译运行程序,目录监视器的界面焕然一新。
除了SkinMagic外还有不少其他公司开发的美化界面的工具,如国产的Skin++等,其使用过程基本大同小异。