Shell扩展编程完全指南(6)如何编写定制”发送到”菜单的Shell扩

Shell扩展编程完全指南

copyright dedecms

linghuye

第六节-如何编写定制发送到菜单的Shell扩展

织梦好,好织梦

简介

本节我将介绍一种比较少用到的扩展类型, 放置目标扩展处理器(drop handler). 这种类型可用于浏览器上的拖放功能, 由被放置到的文件(即放置的目标)类型决定所激发的扩展.

copyright dedecms

第三节假设理解Shell扩展的基础知识(参见第一节), 并熟悉MFC. 如果你需要复习一下要使用的MFC 类, 请阅读第四节, 因为本文将使用相同的技巧. 该代码使用shlwapi.dll 输出的一些函数, 所以你需要安装IE 4 或更高的版本(但你不必安装活动桌面).

dedecms.com

放置目标扩展处理器

在第四节 中, 我讨论了拖放目标扩展处理器, 它在用右键拖放文件时被激发. 浏览器同时也让我们编写扩展来处理用左键拖放文件的操作, 当文件被放下时扩展开始工作. 如, WinZip 包含一个放置处理器来让你添加文件到压缩文件中. 当你拖动一个文件到Zip文件上时, 浏览器会高亮显示Zip文件并显示一个带加号的鼠标以表明zip文件可以是一个放置目标:

本文来自织梦

 [Dropping on a zip - 3K] 织梦内容管理系统

如果没有安装放置目标处理器, 当你将文件拖过Zip文件时就不会发生什么特别的情况:

dedecms.com

 [Dropping on a zip w/o a drop handler - 3K]

织梦好,好织梦

放置扩展处理器只在当你有自定义的文件类型时有用, 如WinZip. 而使用放置目标处理器所作的更有趣的是添加菜单项到发送到菜单. 发送到菜单显示了\Windows\SendTo文件夹中的内容. 一般而言, 发送到文件夹包含快捷方式, 但微软的Power Toys 工具添加几个特殊项,如下所示:

copyright dedecms

 [Sample Send To menu - 17K] copyright dedecms

如果你不清楚放置处理器如何处理, 看一下发送到文件夹的内容:

本文来自织梦

12-02-980:271293?Floppy (A).lnk 本文来自织梦 
11-26-9810:270Any Folder....OtherFolder 织梦内容管理系统 
11-26-9810:270Clipboard as Contents.ContentsOnClipboard 

本文来自织梦

11-26-9810:270Clipboard as Name.NameOnClipboard 

本文来自织梦

11-26-9810:270Command Line.CommandLine 

织梦好,好织梦

3-26-998:420Desktop (create shortcut).DeskLink 内容来自dedecms 
4-22-9923:300Norton Wipe Slack Space.WipeSlack 
织梦内容管理系统
4-22-9923:300Norton WipeInfo.WipeInfo 
内容来自dedecms
11-26-9810:26285 Notepad.lnk 
本文来自织梦
1-07-009:01212xfer directory on zip drive.lnk 
织梦好,好织梦

注意那些古怪的扩展名如".ContentsOnClipboard". 这些0字节的文件被放到该目录下使得在发送到菜单中能显示其相应项目, 而实际的扩展存在注册表中. 但是它们没有正常的文件关联, 因为文件并没有像打开或打印之类的动作. 它们所知的只是放置扩展处理器. 当你点击发送到菜单中的一项, 浏览器会激发相应的放置扩展处理器. 以下标出放置目标的发送到菜单: dedecms.com

 [Send To menu again - 17K] 织梦内容管理系统

本文的例子工程模仿Send To Any Folder Powertoy – 它可以复制或移动文件到系统中的任一文件夹.

织梦内容管理系统

使用AppWizard 开始

运行AppWizard 并生成一个名为SendToCloneATL工程.保留所有默认设置, 点击”完成”. 然后,在ClassView树中右击SendToClone classes项,在弹出的菜单中选择New ATL Object,添加一个COM 对象类到DLL中. 织梦内容管理系统

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

初始化接口

由于放置处理器由放置目标所激发, 使用IPersistFile接口进行初始化. (记住IPersistFile用于一次只操作一个文件的扩展.) IPersistFile接口有多个方法, 但在Shell扩展中只有Load()方法需要实现. 织梦内容管理系统

我们需要添加IPersistFile接口到CSendToShlExt实现的接口中. 打开SendToShlExt.h 文件添加以下标红的代码:

织梦内容管理系统

#include <comdef.h> 织梦内容管理系统 
#include <shlobj.h> copyright dedecms 
 

copyright dedecms

class ATL_NO_VTABLE CSendToShlExt :  dedecms.com 
public CComObjectRootEx<CComSingleThreadModel>, 本文来自织梦 
public CComCoClass<CSendToShlExt, &CLSID_SendToShlExt>, 
织梦好,好织梦
public IDispatchImpl<ISendToShlExt, &IID_ISendToShlExt, &LIBID_SENDTOCLONELib>, 本文来自织梦 
public IPersistFile copyright dedecms 
{ 

织梦内容管理系统

BEGIN_COM_MAP(CSendToShlExt) 本文来自织梦 
COM_INTERFACE_ENTRY(ISendToShlExt) 
copyright dedecms
COM_INTERFACE_ENTRY(IDispatch) copyright dedecms 
COM_INTERFACE_ENTRY(IPersistFile) copyright dedecms 
END_COM_MAP() 

织梦好,好织梦

 
copyright dedecms
public: 
copyright dedecms
// IPersistFile 织梦好,好织梦 
STDMETHOD(GetClassID)(LPCLSID){ return E_NOTIMPL; } 内容来自dedecms 
STDMETHOD(IsDirty)(){ return E_NOTIMPL; } 织梦好,好织梦 
STDMETHOD(Load)(LPCOLESTR, DWORD){ return S_OK;} 织梦好,好织梦 
STDMETHOD(Save)(LPCOLESTR, BOOL){ return E_NOTIMPL; } 
dedecms.com
STDMETHOD(SaveCompleted)(LPCOLESTR) { return E_NOTIMPL; } 

内容来自dedecms

STDMETHOD(GetCurFile)(LPOLESTR*){ return E_NOTIMPL; } 
本文来自织梦

注意Load()方法除了返回S_OK外什么都不做. Load()方法可以接收放置目标文件名, 但对于这个扩展我们不关心这个名称,所以我们忽略它. 本文来自织梦

参与拖放操作

为了完成操作我们的扩展得跟拖放源进行通信, 即浏览器本身. 我们的扩展会取得一个被拖放的文件列表, 接着它通知浏览器它是否接受这些被放置的文件. 这些通信通过另一个接口: IDropTarget. IDropTarget接口方法有: 织梦内容管理系统

  • DragEnter(): 当用户第一次拖动文件经过时被调用. 该方法通知浏览器扩展是否接受当前被拖动的文件.
  • DragOver(): 在Shell 扩展中不会被调用.
  • DragLeave(): 当用户拖动并离开文件时被调用.
  • Drop(): 当用户放下拖动文件时被调用. Shell扩展的主要工作就在这里.

要添加IDropTarget接口到CSendToShlExt, 打开SendToShlExt.h 文件并添加下面标红代码: dedecms.com

class ATL_NO_VTABLE CSendToShlExt :  
dedecms.com
public CComObjectRootEx<CComSingleThreadModel>, 

copyright dedecms

public CComCoClass<CSendToShlExt, &CLSID_SendToShlExt>, copyright dedecms 
public IDispatchImpl<ISendToShlExt, &IID_ISendToShlExt, &LIBID_SENDTOCLONELib>, 
织梦内容管理系统
public IPersistFile, 织梦内容管理系统 
public IDropTarget 织梦内容管理系统 
{ 
织梦好,好织梦
BEGIN_COM_MAP(CSendToShlExt) 
dedecms.com
COM_INTERFACE_ENTRY(ISendToShlExt) 
本文来自织梦
COM_INTERFACE_ENTRY(IDispatch) dedecms.com 
COM_INTERFACE_ENTRY(IPersistFile) dedecms.com 
COM_INTERFACE_ENTRY(IDropTarget) 

内容来自dedecms

END_COM_MAP() copyright dedecms 
 dedecms.com 
protected: dedecms.com 
// ISendToShlExt 织梦内容管理系统 
CStringList m_lsDroppedFiles; 
copyright dedecms
 

copyright dedecms

public: dedecms.com 
// IDropTarget copyright dedecms 
STDMETHOD(DragEnter)(IDataObject* pDataObj, DWORD grfKeyState, 内容来自dedecms 
POINTL pt, DWORD* pdwEffect); copyright dedecms 
 copyright dedecms 
STDMETHOD(DragOver)(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) 

织梦好,好织梦

{ return E_NOTIMPL; } 
本文来自织梦
 

内容来自dedecms

STDMETHOD(DragLeave)(); 
本文来自织梦
 内容来自dedecms 
STDMETHOD(Drop)(IDataObject* pDataObj, DWORD grfKeyState, 织梦内容管理系统 
POINTL pt, DWORD* pdwEffect); dedecms.com 
} 

本文来自织梦

如以前的版本, 我们将使用文件列表来保存被拖放的文件. DragOver()方法不需要实现因为它不会被调用. 我将说明其余三个方法. 织梦内容管理系统

DragEnter()

DragEnter()的原型为:

dedecms.com

HRESULT IDropTarget::DragEnter ( 

织梦好,好织梦

IDataObject* pDataObj, 

本文来自织梦

DWORDgrfKeyState, 

织梦内容管理系统

POINTLpt, 

本文来自织梦

DWORD*pdwEffect ); 内容来自dedecms 

PDataObj是一个IDataObject接口的指针,使用之我们可以列举被拖动的文件. grfKeyState是一组表明Shift键和鼠标键状态的标志. pt是个POINTL结构(其实就是POINT) 保存鼠标的当前位置. pdwEffect是个DWORD 值的指针,我们将用这个值返回通知浏览器我们是否接受该放置文件, 如果接受,该显示什么特别图标覆盖在鼠标上.

本文来自织梦

如前述, DragEnter()当用户拖动文件到目标位置上时被调用. 然而, 当用户点击一项发送到菜单时也被调用,所以我们仍可以在DragEnter()中完成工作即使这里其实并没有拖放操作发生. copyright dedecms

我们的DragEnter()实现填充一个拖放的文件名列表. 该扩展将接受所有文件或文件夹, 因为任一文件系统对象都可以被复制或移动.

织梦好,好织梦

DragEnter()开始部分你一定熟悉– 我们将IdataObject接口赋值给一个COleDataObject变量, 并列举被拖放的文件. dedecms.com

HRESULT CSendToShlExt::DragEnter ( 

织梦好,好织梦

IDataObject* pDataObj, 

本文来自织梦

DWORDgrfKeyState, 

织梦好,好织梦

POINTLpt, copyright dedecms 
DWORD*pdwEffect ) 
本文来自织梦
{ 内容来自dedecms 
AFX_MANAGE_STATE(AfxGetStaticModuleState());// init MFC 织梦好,好织梦 
 
织梦好,好织梦
COleDataObject dataobj; copyright dedecms 
TCHARszItem [MAX_PATH]; 织梦好,好织梦 
UINTuNumFiles; copyright dedecms 
HGLOBALhglobal; 织梦好,好织梦 
HDROPhdrop; 
copyright dedecms
 织梦好,好织梦 
dataobj.Attach ( pDataObj, FALSE ); // attach to the IDataObject, don't auto-release it 
织梦好,好织梦
 
织梦内容管理系统
// 从数据对象中读取文件列表.他们存储在HDROP 格式中 copyright dedecms 
// 因此,取得 HDROP 句柄,并使用拖放API copyright dedecms 
 
织梦好,好织梦
hglobal = dataobj.GetGlobalData ( CF_HDROP ); 织梦好,好织梦 
 本文来自织梦 
if ( NULL != hglobal ) dedecms.com 
{ copyright dedecms 
hdrop = (HDROP) GlobalLock ( hglobal ); dedecms.com 
 织梦内容管理系统 
uNumFiles = DragQueryFile ( hdrop, 0xFFFFFFFF, NULL, 0 ); 内容来自dedecms 
 织梦内容管理系统 
for ( UINT uFile = 0; uFile < uNumFiles; uFile++ ) 内容来自dedecms 
{ dedecms.com 
if ( 0 != DragQueryFile ( hdrop, uFile, szItem, MAX_PATH )) 内容来自dedecms 
{ 内容来自dedecms 
m_lsDroppedFiles.AddTail ( szItem ); 

织梦内容管理系统

} 
织梦好,好织梦
} 内容来自dedecms 
 
织梦好,好织梦
GlobalUnlock ( hglobal ); 织梦内容管理系统 
} 

本文来自织梦

现在返回pdwEffect. 我们可以返回的效果有:

织梦内容管理系统

  • DROPEFFECT_COPY: 通知浏览器我们的扩展将复制拖放的文件.
  • DROPEFFECT_MOVE: 通知浏览器我们的扩展将移动拖放的文件.
  • DROPEFFECT_LINK: 通知浏览器我们的扩展将为拖放的文件创建快捷方式.
  • DROPEFFECT_NONE: 通知浏览器我们的扩展不接受拖放的文件.

我们返回的唯一效果是DROPEFFECT_COPY. 我们不能返回DROPEFFECT_MOVE, 因为这会使浏览器删除被拖放的文件. 我们可以返回DROPEFFECT_LINK, 但光标会显示为创建快捷方式的样子, 这会误导用户. 如果文件列表为空, 我们返回DROPEFFECT_NONE告诉浏览器我们不接受拖放的文件. copyright dedecms

if ( m_lsDroppedFiles.GetCount() > 0 )  织梦好,好织梦 
{ 

织梦内容管理系统

*pdwEffect = DROPEFFECT_COPY; copyright dedecms 
return S_OK; 
织梦内容管理系统
} 

内容来自dedecms

else 

织梦内容管理系统

{ 内容来自dedecms 
*pdwEffect = DROPEFFECT_NONE; 内容来自dedecms 
return E_INVALIDARG; 本文来自织梦 
} 
copyright dedecms
} 

织梦好,好织梦

DragLeave()

DragLeave()当用户拖动并离开文件时被调用. 发送到菜单扩展不使用该方法,但如果你打开发送到菜单文件夹的浏览器窗口并将文件拖到该文件夹下它会被调用. 我们没有什么清理工作要做(CStringList析构器会自动完成), 所以我们只要返回S_OK即可:

dedecms.com

HRESULT CSendToShlExt::DragLeave() 
织梦好,好织梦
{ 

dedecms.com

return S_OK; 
dedecms.com
} 织梦好,好织梦 

Drop()

如果用户选中发送到菜单项, 浏览器调用Drop(), 其原型为:

dedecms.com

HRESULT IDropTarget::Drop ( 本文来自织梦 
IDataObject* pDataObj, 
织梦好,好织梦
DWORDgrfKeyState, 
织梦好,好织梦
POINTLpt, 
内容来自dedecms
DWORD*pdwEffect ); 

dedecms.com

头三个参数同DragEnter(). Drop()应该使用pdwEffect参数返回操作的最终效果. 我们的Drop()函数创建主对话框并传给它文件名列表. 由该对话框完成所有的工作, 而当DoModal()返回时, 我们设置最终的放置效果.

本文来自织梦

HRESULT CSendToShlExt::Drop ( 内容来自dedecms 
IDataObject* pDataObj, 内容来自dedecms 
DWORDgrfKeyState, 织梦好,好织梦 
POINTLpt, 本文来自织梦 
DWORD*pdwEffect ) 内容来自dedecms 
{ 

织梦好,好织梦

AFX_MANAGE_STATE(AfxGetStaticModuleState());// init MFC 

copyright dedecms

 
dedecms.com
CSendToCloneDlg dlg ( &m_lsDroppedFiles ); copyright dedecms 
 

织梦内容管理系统

dlg.DoModal(); dedecms.com 
 copyright dedecms 
*pdwEffect = DROPEFFECT_COPY; 本文来自织梦 
return S_OK; 织梦好,好织梦 
} copyright dedecms 

对话框如下所示:

织梦好,好织梦

 [Send to dialog - 18K]

织梦好,好织梦

这是个简单的MFC 对话框, 你可以在SendToCloneDlg.cpp 文件中找到源代码. 我使用CshellFileOp完成实际的移动和复制,具体参见我的文章"CShellFileOp - Wrapper for SHFileOperation." dedecms.com

但是等等? 我们如何告诉浏览器我们编写的放置处理器的? 我们又怎样获取发送到菜单中的一项? 我会在下一段解释. 织梦好,好织梦

注册Shell 扩展

注册放置处理器与其它扩展有所不同, 因为它需要在HKEY_CLASSES_ROOT 创建一个新的关联. AppWizard生成的RGS 脚本如下所示. 你应该添加标红的代码. dedecms.com

HKCR 内容来自dedecms 
{ 
copyright dedecms
.SendToClone = s 'CLSID\{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}' 织梦内容管理系统 
SendToClone.SendToShlExt.1 = s 'SendToShlExt Class' dedecms.com 
{ 织梦好,好织梦 
CLSID = s '{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}' 内容来自dedecms 
} 内容来自dedecms 
SendToClone.SendToShlExt = s 'SendToShlExt Class' 内容来自dedecms 
{ 织梦内容管理系统 
CLSID = s '{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}' 内容来自dedecms 
CurVer = s 'SendToClone.SendToShlExt.1' 
copyright dedecms
} 

织梦好,好织梦

NoRemove CLSID dedecms.com 
{ 内容来自dedecms 
ForceRemove {B7F3240E-0E29-11D4-8D3B-80CD3621FB09} = s 'Send To Any Folder Clone' 

织梦内容管理系统

{ 

dedecms.com

ProgID = s 'SendToClone.SendToShlExt.1' 织梦好,好织梦 
VersionIndependentProgID = s 'SendToClone.SendToShlExt' 

织梦好,好织梦

ForceRemove 'Programmable' 织梦好,好织梦 
InprocServer32 = s '%MODULE%' 
copyright dedecms
{ 本文来自织梦 
val ThreadingModel = s 'Apartment' copyright dedecms 
} 
内容来自dedecms
'TypeLib' = s '{B7F32400-0E29-11D4-8D3B-80CD3621FB09}' 织梦好,好织梦 
val NeverShowExt = s '' 内容来自dedecms 
DefaultIcon = s '%MODULE%,0' 

织梦内容管理系统

shellex 本文来自织梦 
{ 本文来自织梦 
DropHandler = s '{B7F3240E-0E29-11D4-8D3B-80CD3621FB09}' 内容来自dedecms 
} 内容来自dedecms 
} 
织梦内容管理系统
} 织梦好,好织梦 
} 
dedecms.com

第一行是创建关联. 其创建一个新的放置目标扩展, .SendToClone. 注意.SendToClone键的默认值以"CLSID\"为前缀. 这告诉浏览器描述关联的数据在HKCR\CLSID 下的一个键. 跟一般的关联一样, 其就保存在HKEY_CLASSES_ROOT键下(如, .txt键指向txtfile键), 但把放置目标处理器关联数据注册在它自己的CLSID键下似乎更好, 这样可以保证数据的统一存放. 内容来自dedecms

字符串"Send To Any Folder Clone" 是当你浏览发送到文件夹时显示在浏览器中的文件类型描述. NeverShowExt值用于告诉浏览器不要显示".SendToClone" 扩展名. DefaultIcon键列出了.SendToClone文件所使用的图标的位置. 最后生成带DropHandler子键的shellex键. 由于一种文件类型只能有一个放置扩展处理器, 处理器的GUID 就存在DropHandler键里, 而不是DropHandler 下的子键.

dedecms.com

剩余的细节是要在发送到文件夹中创建一个文件好让显示我们的菜单项. 我们可以在DllRegisterServer()中完成并在DllUnregisterServer() 中删除文件. 下面是创建文件的代码:

内容来自dedecms

LPITEMIDLIST pidl; 本文来自织梦 
TCHARszSendtoPath [MAX_PATH]; 

内容来自dedecms

HANDLEhFile; 

本文来自织梦

LPMALLOCpMalloc; 本文来自织梦 
 
本文来自织梦
if ( SUCCEEDED( SHGetSpecialFolderLocation ( NULL, CSIDL_SENDTO, &pidl ))) 

dedecms.com

{ 本文来自织梦 
if ( SHGetPathFromIDList ( pidl, szSendtoPath )) 
copyright dedecms
{ 内容来自dedecms 
PathAppend ( szSendtoPath, _T("Some other folder.SendToClone") ); copyright dedecms 
 

织梦好,好织梦

hFile = CreateFile ( szSendtoPath, GENERIC_WRITE, FILE_SHARE_READ, 内容来自dedecms 
NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); 内容来自dedecms 
 织梦好,好织梦 
CloseHandle ( hFile ); 内容来自dedecms 
} 

dedecms.com

 

dedecms.com

if ( SUCCEEDED( SHGetMalloc ( &pMalloc ))) 
本文来自织梦
{ 内容来自dedecms 
pMalloc->Free ( pidl ); 
copyright dedecms
pMalloc->Release(); 

织梦好,好织梦

} 织梦好,好织梦 
} 内容来自dedecms 

下面是发送菜单的样子: 织梦内容管理系统

 [New Send To item - 9K] 内容来自dedecms

DllUnregisterServer()删除”发送到”文件夹中的文件. 上面的代码对任何版本的Windows 都适用. 如果你事先知道你的代码会在高于Shell 4.71版本的情况下运行, 你可以使用SHGetSpecialFolderPath()函数而不用SHGetSpecialFolderLocation().

织梦内容管理系统

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

内容来自dedecms

待续...?

嗯, 这篇是我打算写的最后这个系列的最后一篇了, 因为我已介绍完所有有意思的Shell扩展. 如果你对以后的文章有何建议, 欢迎Email 给我, 或者直接在CodeProject上给我意见. 本文来自织梦