CListCtrlEx:一个支持文件拖放和实时监视的列表控件——用未公开API函数实现Shell实时监视
一、需求
无论何时,当你在Explorer窗口中创建、删除或重命名一个文件夹/文件,或者插入拔除移动存储器时,Windows总是能非常快速地更新它所有的视图。有时候我们的程序中也需要这样的功能,以便当用户在Shell中作出创建、删除、重命名或其他动作时,我们的应用程序也能快速地随之更新。
二、原理
Windows内部有两个未公开的函数(注:在最新的MSDN中,已经公开了这两个函数),分别叫做SHChangeNotifyReGISter和SHChangeNotifyDeregister,可以实现以上的功能。这两个函数位于Shell32.dll中,是用序号方式导出的。这就是为什么我们用VC自带的Depends工具察看Shell32.dll时,找不到这两个函数的原因。SHChangeNotifyRegister的导出序号是2;而SHChangeNotifyDeregister的导出序号是4。
SHChangeNotifyRegister可以把指定的窗口添加到系统的消息监视链中,这样窗口就能接收到来自文件系统或者Shell的通知了。而对应的另一个函数,SHChangeNotifyDeregister,则用来取消监视钩挂。SHChangeNotifyRegister的原型和相关参数如下:
ULONG SHChangeNotifyRegister
(
HWND hwnd,
int fSources,
LONG fEvents,
UINT wMsg,
Int cEntries,
SHChangeNotifyEntry *pfsne
);
其中:
hwnd
将要接收改变或通知消息的窗口的句柄。
fSource
指示接收消息的事件类型,将是下列值的一个或多个(注:这些标志没有被包括在任何头文件中,使用者须在自己的程序中加以定义或者直接使用其对应的数值)
SHCNRF_InterruptLevel
0x0001。接收来自文件系统的中断级别通知消息。
SHCNRF_ShellLevel
0x0002。接收来自Shell的Shell级别通知消息。
SHCNRF_RecursiveInterrupt
0x1000。接收目录下所有子目录的中断事件。此标志必须和SHCNRF_InterruptLevel 标志合在一起使用。当使用该标志时,必须同时设置对应的SHChangeNotifyEntry结构体中的fRecursive成员为TRUE(此结构体由函数的最后一个参数pfsne指向),这样通知消息在目录树上是递归的。
SHCNRF_NewDelivery
0x8000。接收到的消息使用共享内存。必须先调用SHChangeNotification_Lock,然后才能存取实际的数据,完成后调用SHChangeNotification_Unlock函数释放内存。
fEvents
要捕捉的事件,其所有可能的值请参见MSDN中关于SHChangeNotify函数的注解。
wMsg
产生对应的事件后,发往窗口的消息。
cEntries
pfsne指向的数组的成员的个数。
pfsne
SHChangeNotifyEntry结构体数组的起始指针。此结构体承载通知消息,其成员个数必须设置成1,否则SHChangeNotifyRegister或者SHChangeNotifyDeregister将不能正常工作(但是据我试验,如果cEntries设为大于1的值,依然可以注册成功,不知何故)。
如果函数调用成功,则返回一个整型注册标志号,否则将返回0。同时系统就会将hwnd指定的窗口加入到操作监视链中,当有文件操作发生时,系统会向hwnd标识的窗口发送wMsg指定的消息,我们只要在程序中加入对该消息的处理函数就可以实现对系统操作的监视了。
如果要退出程序监视,就要调用另外一个未公开得函数SHChangeNotifyDeregister来取消程序监视。该函数的原型如下:
BOOL SHChangeNotifyDeregister(ULONG ulID);
其中ulID指定了要注销的监视注册标志号,如果卸载成功,返回TRUE,否则返回FALSE。
三、实例
在前一文中,我们派生了一个支持文件拖放的列表控件CListCtrlEx,但它还有一个小小的缺憾,就是当用户把一个文件拖放进来之后,如果用户在Shell中对该文件进行移动、删除、重命名等操作时,CListCtrlEx将不能保证实时更新。经过上面的讨论,现在就让我们为CListCtrlEx加上实时文件监控功能。
在使用这两个函数之前,必须要先声明它们的原型,同时还要添加一些宏和结构定义。我们在原工程中添加一个ShellDef.h头文件,然后加入如下声明:
#define SHCNRF_InterruptLevel 0x0001 //Interrupt level notifications from the file system
#define SHCNRF_ShellLevel 0x0002 //Shell-level notifications from the shell
#define SHCNRF_RecursiveInterrupt 0x1000 //Interrupt events on the whole subtree
#define SHCNRF_NewDelivery 0x8000 //Messages received use shared memory
typedef struct
{
LPCITEMIDLIST pidl; //Pointer to an item identifier list (PIDL) for which to receive notifications
BOOL fRecursive; //Flag indicating whether to post notifications for children of this PIDL
}SHChangeNotifyEntry;
typedef struct
{
DWORD dwItem1; // dwItem1 contains the previous PIDL or name of the folder.
DWORD dwItem2; // dwItem2 contains the new PIDL or name of the folder.
}SHNotifyInfo;
typedef ULONG
(WINAPI* pfnSHChangeNotifyRegister)
(
HWND hWnd,
int fSource,
LONG fEvents,
UINT wMsg,
int cEntries,
SHChangeNotifyEntry* pfsne
);
typedef BOOL (WINAPI* pfnSHChangeNotifyDeregister)(ULONG ulID);
这些宏和函数的声明,以及参数含义,如前所述。下面我们要在CListCtrlEx体内添加两个函数指针和一个ULONG型的成员变量,以保存函数地址和返回的注册号。
接下来我们为CListCtrlEx类添加一个公有成员函数Initialize,在其中,我们首先进行加载Shell32.dll以及初始化函数指针动作,接着调用注册函数向Shell注册。用户在使用我们提供的CListCtrlEx类时,应当在CListCtrlEx创建完毕后跟着调用Initialize函数以确保注册了消息监视钩子。否则将失去实时监视功能。初始化函数如下(已略去涉及OLE初始化的部分):
BOOL CListCtrlEx::Initialize()
{
…………
//加载Shell32.dll
m_hShell32 = LoadLibrary("Shell32.dll");
if(m_hShell32 == NULL)
{
return FALSE;
}
//取函数地址
m_pfnDeregister = NULL;
m_pfnRegister = NULL;
m_pfnRegister = (pfnSHChangeNotifyRegister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(2));
m_pfnDeregister = (pfnSHChangeNotifyDeregister)GetProcAddress(m_hShell32,MAKEINTRESOURCE(4));
if(m_pfnRegister==NULL || m_pfnDeregister==NULL)
{
return FALSE;
}
SHChangeNotifyEntry shEntry = {0};
shEntry.fRecursive = TRUE;
shEntry.pidl = 0;
m_ulNotifyId = 0;
//注册Shell监视函数
m_ulNotifyId = m_pfnRegister(
GetSafeHwnd(),
SHCNRF_InterruptLevel|SHCNRF_ShellLevel,
SHCNE_ALLEVENTS,
WM_USERDEF_FILECHANGED, //自定义消息
1,
&shEntry
);
if(m_ulNotifyId == 0)
{
MessageBox("Register failed!","ERROR",MB_OK|MB_ICONERROR);
return FALSE;
}
return TRUE;
}
在CListCtrlEx类中再添加如下函数。该函数的作用是从PIDL中解出实际字符路径。
CString CListCtrlEx::GetPathFromPIDL(DWORD pidl)
{
char szPath[MAX_PATH];
CString strTemp = _T("");
if(SHGetPathFromIDList((struct _ITEMIDLIST *)pidl, szPath))
{
strTemp = szPath;
}
return strTemp;
}
现在我们的程序就可以接收到来自Shell或文件系统的通知消息了,只要编写一个处理自定义消息的函数,就可以对系统范围内的更改动作作出我们希望的响应动作。这里lParam参数中存放的是当前发生的事件(譬如SHCNE_CREATE),wParam参数中存放的是SHNotifyInfo结构。下面是一段框架代码:
LRESULT CListCtrlEx::OnFileChanged(WPARAM wParam, LPARAM lParam)
{
CString strOriginal = _T("");
CString strCurrent = _T("");
SHNotifyInfo* pShellInfo = (SHNotifyInfo*)wParam;
strOriginal = GetPathFromPIDL(pShellInfo->dwItem1);
if(strOriginal.IsEmpty())
{
return NULL;
}
switch(lParam)
{
case SHCNE_CREATE:
break;
case SHCNE_DELETE:
break;
case SHCNE_RENAMEITEM:
break;
}
return NULL;
}
四、总结
至此我们完成了对CListCtrlEx的扩充工作,通过这两个未公开的API函数,编写一个文件夹/文件监视器不再是一件难事。当然同样的功能也可以通过编写设备驱动程序来,但这种方法来实现,难度大,周期长,开发上也有不少困难。
CListCtrlEx:一个支持文件拖放和实时监视的列表控件——用未公开API函数实现Shell实时监视的更多相关文章
- MFC_VC++_时间获取与保存列表控件内容到文件操作方法
MFC_VC++_时间获取与保存列表控件内容到excel文件操作方法 void CDataView::OnBnClickedBtnExporttoexcel() { CTime time = CTim ...
- 一个类似抖音 APP 拍摄按钮效果的控件
TouchButton 一个类似抖音 APP 拍摄按钮效果的控件 效果图预览 用法 <net.angrycode.library.TouchButton android:id="@+i ...
- wxpython 支持python语法高亮的自定义文本框控件的代码
在研发闲暇时间,把开发过程中比较重要的一些代码做个珍藏,下面的代码内容是关于wxpython 支持python语法高亮的自定义文本框控件的代码,应该是对大家也有用. import keywordimp ...
- Android TV开发总结(七)构建一个TV app中的剧集列表控件
原文:Android TV开发总结(七)构建一个TV app中的剧集列表控件 版权声明:我已委托"维权骑士"(rightknights.com)为我的文章进行维权行动.转载务必转载 ...
- android 支持上拉加载,下拉刷新的列表控件SwipeRefreshLayout的二次封装
上拉加载,下拉刷新的列表控件,大家一定都封装过,或者使用过 源代码,我会在最后贴出来 这篇代码主要是为了解决两个问题 1.滑动冲突得问题 2.listview无数据时,无数据布局的展示问题 下方列出的 ...
- C# 曲线控件 曲线绘制 实时曲线 多曲线控件 开发
Prepare 本文将使用一个NuGet公开的组件来实现曲线的显示,包含了多种显示的模式和配置来满足各种不同的应用场景,方便大家进行快速的开发系统. 在Visual Studio 中的NuGet管理器 ...
- 支持.NET和移动设备的XLS读写控件XLSReadWriteII下载地址及介绍
原文来自龙博方案网http://www.fanganwang.com/product/3085转载请注明出处 读写任何单元值 数字型.字符型.布尔型以及错误型.但是你了解日期和时间型单元吗?在Exce ...
- HTML控件ID和NAME属性的区别,以及如何在asp.net页面的.CS文件中获得.ASPX页面中HTML控件的值
在html中:name指的是用户名称,ID指的是用户注册是系统自动分配给用户的一个序列号. name是用来提交数据的,提供给表单用,可以重复: id则针对文档操作时候用,不能重复.如:document ...
- android假设重写onDraw实现一个相似TextView能够显示表情和链接的控件(一)
先看效果图: 写一个超连接支持的对象: /**作为超连接显示的对象*/ public class LinkInfo implements Comparable<LinkInfo>{ pri ...
随机推荐
- Android sdk安装目录中没有platform-tools目录问题详解
sdk下载地址 http://tools.android-studio.org/index.php/sdk 安装步骤很简单,百度即可. 下面详细说一下,在安装中遇到android sdk下没有plat ...
- php中数据库连接方式pdo和mysqli对比分析
1)总的比较 PDO MySQLi 数据库支持 12种不同的数据库支持 支持MySQL API OOP OOP + 过程 Connection Easy Easy 命名参数 支持 不支持 对象映射 ...
- MySQL乱码问题以及utf8mb4字符集
MySQL乱码问题以及utf8mb4字符集 1.乱码 推荐大家看 深入MySQL字符集设置 ,区分检查client端.server端的编码:最简单暴力的方式,是在所有的环节都显式明确的指定相同的编码, ...
- 读写分离MYSQL类
2014年4月27日 12:34:08 概述: 1. 根据sql语句判断是连接读库还是写库 2. 链式调用$this->where()->get() 3. 不同的主机对应不同的实例, 不再 ...
- idea如何编译maven项目
这里我们介绍两种方式,如何调试出窗口 点击菜单栏View->Tool Windows->Maven projects 点击菜单栏Help->Find Action(Ctrl+Shi ...
- CentOS7上安装与配置Tomcat8与MySQL5.7
一.安装tomcat Tomcat 的安装依赖 JDK,在安装 Tomcat 之前需要先安装 Java JDK.输入命令 java -version,如果显示 JDK 版本,证明已经安装了 JDK.
- Fiddler实现移动端手机抓包
Fiddler是一个http调试代理,它能 够记录所有的你电脑和互联网之间的http通讯,Fiddler 可以也可以让你检查所有的http通讯,设置断点,以及Fiddle 所有的“进出”的数据(指co ...
- CSS3实现各种表情
CSS3实现各种表情 效果图: 代码如下,复制即可使用: <!DOCTYPE html> <html> <head> <title></title ...
- CAS5.2x单点登录(一)——搭建cas服务器
系列文章列表: https://blog.csdn.net/u013825231/article/category/7517313 单点登录的介绍 单点登录(Single Sign On ,简称SSO ...
- mysql 索引理解
数据的查询,都需要将数据从磁盘中加载到内存中进行运算加载,索引的出现,让原来每个数据块做一次IO减少为区间范围的快速定位,来减少块的io次数. 如上图,是一颗b+树,关于b+树的定义可以参见B+树,这 ...