7.6 【实例】目录监视器

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++等,其使用过程基本大同小异。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值