Shell扩展编程完全指南(3)如何编写为文件对象弹出信息框的Shell

Shell扩展编程完全指南 织梦内容管理系统

linghuye 织梦内容管理系统

第三节-如何编写为文件对象弹出信息框的Shell扩展

织梦好,好织梦

在第一 和二 节中, 我说明了如何编写上下文菜单扩展. 在第三节中, 我将示范一种新的扩展类型, 解释如何使用Shell进程的内存, 及如何在ATL中使用MFC.

copyright dedecms

第三节假设你理解Shell扩展的基础知识(参见第一节), 并熟悉MFC. 注意这一节的扩展需要4.71或更高的版本, 所以你必须运行在Windows 98 或2000, 或者在95 或NT 4下安装活动桌面特性.

内容来自dedecms

QueryInfo扩展

活动桌面引入一项新特性, 当你在某些特定对象上盘旋鼠标时,工具提示将显示它们的描述. 例如, 在我的电脑上盘旋鼠标时将显示如下提示:

copyright dedecms

 [My Computer tooltip - 6K] 本文来自织梦

其它对象如网络邻居和控制面板都有类似的提示. 我们可以使用QueryInfo扩展为Shell中的其它对象提供自定义的工具提示.

织梦好,好织梦

"QueryInfo 扩展"名称的含义: 这名称是我自己起的; 我用其使用的接口IqueryInfo来命名该扩展. 到目前为止, 它还没有一个正式的名称. 我看了一下1999年10月的MSDN 也没发现关于这种扩展的任何介绍! 但它确实是一个被支持的扩展,因为Microsoft Office 也为它的文件类型安装了QueryInfo 扩展, 如下所示:

copyright dedecms

 [Word doc tooltip - 5K]

本文来自织梦

WinZip 版本8 也为压缩文件类型安装有QueryInfo 扩展: 织梦好,好织梦

 [WinZip tooltip - 5K]

本文来自织梦

我发现这方面最好的文档是MSDN杂志2000年3月的Dino Esposito的文章"使用新的工具提示和图标覆盖Shell扩展改善你的用户界面" . 本文来自织梦

开始编写上下文菜单– 它该做些什么?

本文的Shell扩展是个文本文件的快速浏览工具– 它会显示文本文件的第一行以及文件大小. 我们的信息将显示在工具提示窗口中,当用户在TXT 文件上盘旋鼠标.

copyright dedecms

使用AppWizard 开始

运行AppWizard 并生成一个名为TxtInfoATL工程. 由于这次我们要使用MFC,所以要选中Support MFC设置, 点击”完成”. 然后,在ClassView树中右击TxtInfo classes项,在弹出的菜单中选择New ATL Object,添加一个COM 对象类到DLL中. copyright dedecms

在ATL Object Wizard 中, 第一页面已经选中了Simple Object, 因此, 单击Next. 在第二页面中, 在Short Name编辑框中输入TxtInfoShlExt点击OK. (其余编辑框中的内容将自动填写.) 这样就创建了一个名为CTxtInfoShlExt 的已实现基本的COM接口的新COM对象类. 我们将在这个类中添加实现代码.

织梦好,好织梦

如果你仔细看一下ClassView 树, 你会发现有个CTxtInfoApp类, 它是从CwinApp 派生的. 该类和全局变量theApp, 使得我们可以使用MFC, 正像我们写一个非ATL的平常的MFC DLL.

织梦内容管理系统

初始化接口

对于每个上下文菜单扩展, 我们都要实现IShellExtInit接口, 这也正是浏览器初始化我们对象的地方. 有些Shell扩展使用另外一个初始化接口, IPersistFile, QueryInfo 扩展就是这样. 有何区别呢?如果你还记得, IShellExtInit::Initialize()接收一个IDataObject指针,使用之可以列举所选的多个文件. 而一次只处理一个文件的扩展可以使用IPersistFile. 由于鼠标不能同时在一个以上的对象上盘旋, 所以QueryInfo扩展一次只处理一个文件,因此它使用IPersistFile.

织梦内容管理系统

开始我们需要添加IPersistFileCTxtInfoShlExt实现的接口列表中.打开TxtInfoShlExt.h, 并添加如下代码:

copyright dedecms

#include <comdef.h> dedecms.com 
#include <shlobj.h> 
dedecms.com
 
dedecms.com
class ATL_NO_VTABLE CTxtInfoShlExt :  
dedecms.com
public CComObjectRootEx<CComSingleThreadModel>, 
本文来自织梦
public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>, 
dedecms.com
public IDispatchImpl<ITxtInfoShlExt, &IID_ITxtInfoShlExt, &LIBID_TXTINFOLib>, dedecms.com 
public IPersistFile 
本文来自织梦
{ 织梦好,好织梦 
BEGIN_COM_MAP(CTxtInfoShlExt) 
织梦好,好织梦
COM_INTERFACE_ENTRY(ITxtInfoShlExt) 
dedecms.com
COM_INTERFACE_ENTRY(IDispatch) 织梦内容管理系统 
COM_INTERFACE_ENTRY(IPersistFile) 内容来自dedecms 
END_COM_MAP() 
dedecms.com

我们需要一个保存浏览器给出的文件名的变量:

本文来自织梦

protected: 
dedecms.com
// ITxtInfoShlExt 织梦内容管理系统 
CString m_sFilename; 本文来自织梦 

注意我们可以在任何地方使用MFC 对象. 内容来自dedecms

如果你看一下IpersistFile 的文档, 你会看到很多方法. 幸运的是, 对于Shell扩展, 我们只用实现Load(), 而忽略其它方法. 以下是IPersistFile方法的原型:

dedecms.com

public: 

织梦内容管理系统

// IPersistFile 

本文来自织梦

STDMETHOD(GetClassID)(LPCLSID){ return E_NOTIMPL; } 

本文来自织梦

STDMETHOD(IsDirty)(){ return E_NOTIMPL; } copyright dedecms 
STDMETHOD(Load)(LPCOLESTR, DWORD); 

copyright dedecms

STDMETHOD(Save)(LPCOLESTR, BOOL){ return E_NOTIMPL; } 
本文来自织梦
STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; } 
织梦好,好织梦
STDMETHOD(GetCurFile)(LPOLESTR*){ return E_NOTIMPL; } 

copyright dedecms

除开Load()外的方法都只返回E_NOTIMPL以表明我们没有实现它们. 本文来自织梦

更妙的是, Load()方法也相当简单. 我们只需保存浏览器传给我们的文件名. 也就是当前鼠标在其上盘旋的文件.

内容来自dedecms

HRESULT CTxtInfoShlExt::Load ( LPCOLESTR wszFilename, DWORD dwMode ) 本文来自织梦 
{ 
织梦好,好织梦
AFX_MANAGE_STATE(AfxGetStaticModuleState());// init MFC 本文来自织梦 
 dedecms.com 
// 让CString 自动转化文件名为 ANSI 字符. 

copyright dedecms

m_sFilename = wszFilename; 
织梦内容管理系统
 

本文来自织梦

return S_OK; 织梦内容管理系统 
} copyright dedecms 

请注意函数的第一行. 要让MFC 正确地工作该行代码是必要的. 由于我们的DLL 要被非MFC 程序所调用, 任一个使用MFC的输出函数必须手动初始化MFC. 如果你不写这行代码, 则许多MFC函数(大多是与资源处理有关的函数) 将失败或出错.

织梦内容管理系统

文件名被保存在m_sFilename 以备后用. 注意我利用了CString的赋值操作符的特性来转化字符串为ANSI格式-如果该DLL 以ANSI方式建立. copyright dedecms

创建工具提示的文本

在浏览器调用了我们的Load()方法之后, 它接着调用QueryInterface()获取另一个接口: IQueryInfo. IQueryInfo是个相当简单的接口,只有两个接口(而其中也只有一个被真正使用). 打开TxtInfoShlExt.h,添加如下标红的代码:

织梦好,好织梦

class ATL_NO_VTABLE CTxtInfoShlExt :  
内容来自dedecms
public CComObjectRootEx<CComSingleThreadModel>, 
copyright dedecms
public CComCoClass<CTxtInfoShlExt, &CLSID_TxtInfoShlExt>, 
内容来自dedecms
public IDispatchImpl<ITxtInfoShlExt, &IID_ITxtInfoShlExt, &LIBID_TXTINFOLib>, dedecms.com 
public IPersistFile, 本文来自织梦 
public IQueryInfo 织梦内容管理系统 
{ 内容来自dedecms 
BEGIN_COM_MAP(CTxtInfoShlExt) 
织梦好,好织梦
COM_INTERFACE_ENTRY(ITxtInfoShlExt) 
dedecms.com
COM_INTERFACE_ENTRY(IDispatch) 本文来自织梦 
COM_INTERFACE_ENTRY(IPersistFile) 
织梦好,好织梦
COM_INTERFACE_ENTRY(IQueryInfo) 织梦内容管理系统 
END_COM_MAP() 织梦好,好织梦 

然后添加IQueryInfo方法的实现:

织梦内容管理系统

// IQueryInfo copyright dedecms 
STDMETHOD(GetInfoFlags)(DWORD*){ return E_NOTIMPL; } 
dedecms.com
STDMETHOD(GetInfoTip)(DWORD, LPWSTR*); 
copyright dedecms

GetInfoFlags() 方法当前并不使用, 所以我们只返回E_NOTIMPL. GetInfoTip() 我们返回工具提示文本字符串. 首先是开头繁琐的代码:

dedecms.com

HRESULT CTxtInfoShlExt::GetInfoTip ( dedecms.com 
DWORDdwFlags, 

dedecms.com

LPWSTR* ppwszTip ) 织梦内容管理系统 
{ 
织梦内容管理系统
AFX_MANAGE_STATE(AfxGetStaticModuleState());// init MFC 
本文来自织梦
 内容来自dedecms 
LPMALLOCpMalloc; 
织梦好,好织梦
CStdioFile file; 本文来自织梦 
DWORDdwFileSize; 

织梦内容管理系统

CStringsFirstLine; 织梦好,好织梦 
BOOLbReadLine; 
织梦好,好织梦
CStringsTooltip; 
dedecms.com
 织梦好,好织梦 
USES_CONVERSION; 

本文来自织梦

接着, AFX_MANAGE_STATE首先被调用以初始化MFC. 这是每个函数都该做的第一件事, 甚至应该在变量声明之前,因为MFC构造函数可能调用其它的MFC 函数.

copyright dedecms

dwFlags当前并不被使用. ppwszTip是个LPWSTR (Unicode 字符串指针) 变量的指针,我们要将其赋值为我们所分配的字符串缓冲区的指针.(指向指针的指针)

本文来自织梦

首先, 我们试着打开文件读取. 由于我们在Load()中保存了文件名,现在就可以使用了.

dedecms.com

if ( !file.Open ( m_sFilename , CFile::modeRead | CFile::shareDenyWrite )) 
织梦内容管理系统
return E_FAIL; 

dedecms.com

现在, 我们需要使用Shell的内存分配器分配一个缓冲, 我们通过SHGetMalloc()函数获取一个IMalloc接口. :

copyright dedecms

if ( FAILED( SHGetMalloc ( &pMalloc ))) 

织梦好,好织梦

return E_FAIL; 

dedecms.com

关于Imalloc 稍后我有更多的要说. 下一步是取得文件大小并读取第一行:

dedecms.com

// 取得文件大小. 

织梦好,好织梦

dwFileSize = file.GetLength(); copyright dedecms 
 
本文来自织梦
// 读取第一行. dedecms.com 
bReadLine = file.ReadString ( sFirstLine ); 本文来自织梦 

bReadLine总是为真, 除非文件不可获取或长度为0 . 下一步是创建工具提示的第一部分:文件大小.

本文来自织梦

sTooltip.Format ( _T("File size: %lu"), dwFileSize ); 
织梦内容管理系统

现在, 我们读取第一行并添加到工具提示中. dedecms.com

if ( bReadLine ) 
dedecms.com
{ 
copyright dedecms
sTooltip += _T("\n"); 内容来自dedecms 
sTooltip += sFirstLine; 

copyright dedecms

} 
织梦好,好织梦

现在我们完成了工具提示, 我们要分配一个缓冲.在这我们将使用Imalloc 接口. 由SHGetMalloc()返回的指针是一个Shell的Imalloc接口指针的拷贝. 我们用这个接口分配的任何内存都位于Shell的进程空间内, 所以Shell可以使用它. 更重要的是, Shell可以释放它. 所以我们所作的就是分配缓冲区,然后忘掉它. Shell将在完成操作时释放该内存.

内容来自dedecms

要认识到的一件事是我们返回给Shell的字符串必须是Unicode 格式的. 这就是为什么下面的Alloc()中的计算要乘以sizeof(wchar_t); 只分配lstrlen(sToolTip)长的内存只够一半所需的内存.

织梦好,好织梦

*ppwszTip = (LPWSTR) pMalloc->Alloc ( (1 + lstrlen(sTooltip)) * sizeof(wchar_t) ); 
copyright dedecms
 

织梦内容管理系统

if ( NULL == *ppwszTip ) 本文来自织梦 
{ 织梦好,好织梦 
pMalloc->Release(); 

内容来自dedecms

return E_OUTOFMEMORY; 

织梦内容管理系统

} copyright dedecms 
 本文来自织梦 
// 使用 Unicode 字符串拷贝函数将工具提示文本拷入缓冲区. 
copyright dedecms
wcscpy ( *ppwszTip, T2COLE((LPCTSTR) sTooltip) ); 本文来自织梦 

最后我们释放先前获取得IMalloc接口.

本文来自织梦

pMalloc->Release(); 
dedecms.com
return S_OK; dedecms.com 
} dedecms.com 

完了! 浏览器将从*ppwszTip中获得字符串并显示在工具提示上.

copyright dedecms

 [text file tooltip - 6K]

dedecms.com

注册Shell扩展

QueryInfo 扩展的注册与上下文菜单有所不同. 我们的扩展注册在HKEY_CLASSES_ROOT下的一个以文件扩展名为名称的子键. 在这个例子中是HKCR\.txt. 但等一等, 有些奇怪! 你可能认为ShellEx子键会是某些有意义的字符串如"TooltipHandlers". 但,这个键名为"{00021500-0000-0000-C000-000000000046}".

织梦好,好织梦

我认为微软是对我们有意隐瞒一些Shell扩展! 如果你研究一下注册表, 你会发现其它的一些以GUID 为名的ShellEx子键. 上面的GUID 恰巧就是IqueryInfo GUID of .

织梦好,好织梦

不论怎样, 以下是我们的Shell扩展所需的RGS 脚本文件: 内容来自dedecms

HKCR copyright dedecms 
{ 

copyright dedecms

NoRemove .txt 

织梦内容管理系统

{ 织梦内容管理系统 
NoRemove shellex dedecms.com 
{ 织梦内容管理系统 
NoRemove {00021500-0000-0000-C000-000000000046} = s '{F4D78AE1-05AB-11D4-8D3B-444553540000}' 

本文来自织梦

} 

本文来自织梦

} copyright dedecms 
} 

内容来自dedecms

你也可以通过改变".txt"为你想要的扩展名,而让扩展为其它文件类型所激发. 不幸的是,你不能在*AllFileSystemObjects下注册QueryInfo 扩展来让你的扩展被所有文件类型激发.

内容来自dedecms

正如上一节的例子一样,在NT/2000上我们需要添加我们的扩展到"approved" 扩展列表中去. 完成该工作的代码在DllRegisterServer()DllUnregisterServer()函数中.我不在这写出这些代码, 因为这只是简单的注册表获取, 你可以在例子工程代码中找到它.

本文来自织梦

待续...

在接下来的第四节 中, 我们将继续讨论上下文菜单并介绍另一种新的扩展类型, 拖放目标处理器. 我们也将看到更多的MFC 使用.

本文来自织梦