Shell扩展编程完全指南(7)自画上下文菜单项的Shell扩展

Shell扩展编程完全指南

copyright dedecms

linghuye
第七节-如何编写自画上下文菜单项的Shell扩展, 以及如何使上下文菜单扩展响应文件夹窗口背景上的鼠标右击事件

织梦好,好织梦

下载示例工程 (两个shell 扩展) - 33K

dedecms.com

本指南开始新的一章! 在这一节里, 我将回答一些读者的要求并讨论两个话题: 在上下文菜单中使用自画功能, 并使上下文菜单扩展响应文件夹窗口背景上的鼠标右击事件. 你应该先读一下第一节和第二节, 学习一下基本的上下文菜单扩展的编写. 织梦内容管理系统

扩展1 – 自画菜单项

在本节, 我将只讨论实现自画菜单的额外工作. copyright dedecms

由于该扩展要实现自画菜单, 这得作些画图的工作. 我决定从程序PicaView 里复制一些代码: 在上下文菜单上显示一个图象文件的缩略图. 如下所示:

织梦内容管理系统

 [PicaView owner-drawn menu item - 10K] 织梦好,好织梦

该扩展将创建BMP 文件的缩略图, 为了使代码尽量简单, 我将不考虑图象的比例和颜色问题. 读者可自行完善这些代码. ;)

内容来自dedecms

使用AppWizard 开始

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

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

本文来自织梦

初始化接口

如我们前面的上下文菜单扩展一样, 浏览器通过IShellExtInit接口让我们进行初始化.我们需要添加IShellExtInit接口到CBmpCtxMenuExt实现的接口列表中. 打开BmpCtxMenuExt.h,并添加如下标红的代码:

织梦好,好织梦

这里还有几个成员变量用于菜单项作图.

copyright dedecms

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

织梦好,好织梦

class ATL_NO_VTABLE CBmpCtxMenuExt :  本文来自织梦 
public CComObjectRootEx<CComSingleThreadModel>, 织梦好,好织梦 
public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>, 内容来自dedecms 
public IDispatchImpl<IBmpCtxMenuExt, &IID_IBmpCtxMenuExt, &LIBID_BMPVIEWEREXTLib>, 

本文来自织梦

public IShellExtInit copyright dedecms 
{ 
dedecms.com
BEGIN_COM_MAP(CBmpCtxMenuExt) copyright dedecms 
COM_INTERFACE_ENTRY(IBmpCtxMenuExt) dedecms.com 
COM_INTERFACE_ENTRY(IDispatch) 

copyright dedecms

COM_INTERFACE_ENTRY(IShellExtInit) 内容来自dedecms 
END_COM_MAP() 织梦内容管理系统 
 
本文来自织梦
public: 织梦内容管理系统 
// IShellExtInit 织梦内容管理系统 
STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY); 织梦内容管理系统 
 
织梦内容管理系统
protected: copyright dedecms 
TCHARm_szFile[MAX_PATH]; 
内容来自dedecms
CBitmap m_bmp; 

本文来自织梦

UINTm_uOurItemID; 

copyright dedecms

 织梦内容管理系统 
LONG m_lItemWidth, m_lItemHeight; dedecms.com 
LONG m_lBmpWidth, m_lBmpHeight; 织梦内容管理系统 
 
织梦好,好织梦
static const LONG m_lMaxThumbnailSize; dedecms.com 
static const LONG m_l3DBorderWidth; 
dedecms.com
static const LONG m_lMenuItemSpacing; copyright dedecms 
static const LONG m_lTotalBorderSpace; 内容来自dedecms 
 织梦内容管理系统 
// Helper functions for handling the menu-related messages. 

copyright dedecms

STDMETHOD(MenuMessageHandler)(UINT, WPARAM, LPARAM, LRESULT*); 内容来自dedecms 
STDMETHOD(OnMeasureItem)(MEASUREITEMSTRUCT*, LRESULT*); dedecms.com 
STDMETHOD(OnDrawItem)(DRAWITEMSTRUCT*, LRESULT*); 织梦好,好织梦 
}; 

内容来自dedecms

IShellExtInit::Initialize()中我们取得被选择右击的文件名, 如果其扩展名为.BMP就为它创建一幅缩略图.

dedecms.com

BmpCtxMenuExt.cpp中添加如下静态变量的声明. 这些变量将控制缩略图的外观. 你也可以随意更改这些值来看一下作图后有何效果.

织梦内容管理系统

const LONG CBmpCtxMenuExt::m_lMaxThumbnailSize = 64; 

内容来自dedecms

const LONG CBmpCtxMenuExt::m_l3DBorderWidth= 2; 内容来自dedecms 
const LONG CBmpCtxMenuExt::m_lMenuItemSpacing= 4; dedecms.com 
const LONG CBmpCtxMenuExt::m_lTotalBorderSpace = 2*(m_lMenuItemSpacing+m_l3DBorderWidth); 
内容来自dedecms

这些变量的含义如下:

本文来自织梦

  • m_lMaxThumbnailSize: 如果位图的任何一边大于这个值, 该位图就会被缩小为一个正方形, 每一边长都为m_lMaxThumbnailSize个象素. 如果位图的每边都小于这个值,位图就按原样显示.
  • m_l3DBorderWidth: 缩略图的3D 边框的凹度,以象素为单位.
  • m_lMenuItemSpacing: 3D边框周围留空的大小. 这给缩略图与周围的菜单项之间留下间距.

添加IShellExtInit::Initialize()函数的实现定义:

copyright dedecms

STDMETHODIMP CBmpCtxMenuExt::Initialize ( 

织梦内容管理系统

LPCITEMIDLIST pidlFolder, 内容来自dedecms 
LPDATAOBJECTpDO, 本文来自织梦 
HKEYhkeyProgID ) 
织梦好,好织梦
{ dedecms.com 
AFX_MANAGE_STATE(AfxGetStaticModuleState()); copyright dedecms 
 内容来自dedecms 
COleDataObject dataobj; 
copyright dedecms
HGLOBALhglobal; 织梦好,好织梦 
HDROPhdrop; 

织梦内容管理系统

boolbOK = false; 

copyright dedecms

 
本文来自织梦
dataobj.Attach ( pDO, FALSE );// FALSE表示当该对象销毁时,不要释放IDataObject 接口 
织梦内容管理系统
 
内容来自dedecms
// 取得第一个所选择的文件名.我只简单地检查一下文件名是不是.BMP文件. 织梦内容管理系统 
hglobal = dataobj.GetGlobalData ( CF_HDROP ); copyright dedecms 
 
dedecms.com
if ( NULL == hglobal ) copyright dedecms 
return E_INVALIDARG; 

织梦好,好织梦

 织梦内容管理系统 
hdrop = (HDROP) GlobalLock ( hglobal ); 
copyright dedecms
 copyright dedecms 
if ( NULL == hdrop ) dedecms.com 
return E_INVALIDARG; 织梦好,好织梦 
 

copyright dedecms

// 取得文件名. 

本文来自织梦

if ( DragQueryFile ( hdrop, 0, m_szFile, MAX_PATH )) 本文来自织梦 
{ 
本文来自织梦
// 是否以 .BMP为扩展名? 
本文来自织梦
if ( PathMatchSpec ( m_szFile, _T("*.bmp") )) 本文来自织梦 
{ 

本文来自织梦

// 加载位图并赋值给一个 CBitmap 对象 织梦好,好织梦 
HBITMAP hbm = (HBITMAP) LoadImage ( NULL, m_szFile, IMAGE_BITMAP, 0, 0,  copyright dedecms 
LR_LOADFROMFILE ); 
本文来自织梦
 
copyright dedecms
if ( NULL != hbm ) 内容来自dedecms 
{ 本文来自织梦 
// 我们加载位图, 赋给CBitmap 对象. 内容来自dedecms 
m_bmp.Attach ( hbm ); 本文来自织梦 
bOK = true; 织梦好,好织梦 
} dedecms.com 
} 
织梦好,好织梦
} dedecms.com 
 

内容来自dedecms

GlobalUnlock ( hglobal ); 

dedecms.com

 本文来自织梦 
return bOK ? S_OK : E_FAIL; 

copyright dedecms

} 织梦好,好织梦 

这是很直接的代码.我们加载位图并赋值给一个CBitmap 对象以备后用.

本文来自织梦

与上下文菜单进行交互

如前, 如果IShellExtInit::Initialize()返回S_OK, 浏览器会接着查询IContextMenu接口. 为了能添加自画菜单项功能, 它还会查询IContextMenu3 接口. IContextMenu3接口给IContextMenu接口加了一个自画功能的方法.

织梦内容管理系统

还有一个IContextMenu2接口, 微软说Shell 版本4.00支持它, 但在Windows 95 上Shell从不查询IContextMenu2 接口, 以至于在Shell 版本4.00上不能实现自画菜单. (NT 4 可能会不同; 但我没试过.) 你可以使菜单项直接显示一幅位图, 但当它被选择时很难看. (这也是PicaView 的做法.)

内容来自dedecms

IContextMenu3IContextMenu2 继承, 添加了HandleMenuMsg2()方法到IcontextMenu 接口. 该方法让我们可以响应两条消息: WM_MEASUREITEMWM_DRAWITEM. 文档说HandleMenuMsg2()WM_INITMENUPOPUPWM_MENUCHAR消息发生时也会被调用, 但是我在Shell 4.72 (Win 95) 和5.00 (Win 2K)中测试时发现不行, HandleMenuMsg2()不会收到这两条消息. 内容来自dedecms

由于IContextMenu3继承于IContextMenu2(其又继承于IContextMenu), 我们只需让我们的类继承IContextMenu3 接口. 打开BmpCtxMenuExt.h添加如下标红的代码: copyright dedecms

class ATL_NO_VTABLE CBmpCtxMenuExt :  dedecms.com 
public CComObjectRootEx<CComSingleThreadModel>, 本文来自织梦 
public CComCoClass<CBmpCtxMenuExt, &CLSID_BmpCtxMenuExt>, dedecms.com 
public IDispatchImpl<IBmpCtxMenuExt, &IID_IBmpCtxMenuExt, &LIBID_BMPVIEWEREXTLib>, 织梦内容管理系统 
public IShellExtInit, 
织梦内容管理系统
public IContextMenu3 
本文来自织梦
{ 

织梦好,好织梦

BEGIN_COM_MAP(CSimpleShlExt) copyright dedecms 
COM_INTERFACE_ENTRY(ISimpleShlExt) 
本文来自织梦
COM_INTERFACE_ENTRY(IDispatch) 本文来自织梦 
COM_INTERFACE_ENTRY(IShellExtInit) dedecms.com 
COM_INTERFACE_ENTRY(IContextMenu) 
dedecms.com
COM_INTERFACE_ENTRY(IContextMenu2) 

内容来自dedecms

COM_INTERFACE_ENTRY(IContextMenu3) 本文来自织梦 
END_COM_MAP() 织梦好,好织梦 
 copyright dedecms 
public: 织梦好,好织梦 
// IContextMenu 
dedecms.com
STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT); 

织梦好,好织梦

STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO); 

本文来自织梦

STDMETHOD(GetCommandString)(UINT_PTR, UINT, UINT*, LPSTR, UINT); 
本文来自织梦
 织梦内容管理系统 
// IContextMenu2 织梦好,好织梦 
STDMETHOD(HandleMenuMsg)(UINT, WPARAM, LPARAM); 织梦好,好织梦 
 
织梦好,好织梦
// IContextMenu3 

内容来自dedecms

STDMETHOD(HandleMenuMsg2)(UINT, WPARAM, LPARAM, LRESULT*); 
织梦好,好织梦

修改上下文菜单

如前, 我们处理IContextMenu的三个方法. 我们在QueryContextMenu() 中加入一个菜单项. 我们先检查一下Shell的版本. 如果为4.71 或更高那我们就可以添加一个自画菜单项. 否则, 我们直接添加一个位图菜单项. 对于后者, 没别的什么工作了; 菜单会自动管理位图的显示. dedecms.com

首先是检查版本的代码. 它调用输出的DllGetVersion()函数来获取版本信息. 如果没有输出该函数, 那它就是4.00版本, 因为版本4.00没有提供DllGetVersion()函数.

织梦好,好织梦

STDMETHODIMP CBmpCtxMenuExt::QueryContextMenu ( 

织梦好,好织梦

HMENU hmenu, 
内容来自dedecms
UINT uIndex, 
dedecms.com
UINT uidCmdFirst, dedecms.com 
UINT uidCmdLast, 本文来自织梦 
UINT uFlags ) 内容来自dedecms 
{ dedecms.com 
// 如果 CMF_DEFAULTONLY 标志被设置我们不作任何操作. 
织梦好,好织梦
if ( uFlags & CMF_DEFAULTONLY ) 织梦好,好织梦 
{ dedecms.com 
return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 ); 本文来自织梦 
} 内容来自dedecms 
 织梦好,好织梦 
bool bUseOwnerDraw = false; 

copyright dedecms

HINSTANCE hinstShell; 
织梦内容管理系统
 本文来自织梦 
hinstShell = GetModuleHandle ( _T("shell32") ); 
dedecms.com
 

本文来自织梦

if ( NULL != hinstShell ) 织梦好,好织梦 
{ 
copyright dedecms
DLLGETVERSIONPROC pProc; 
内容来自dedecms
 织梦好,好织梦 
pProc = (DLLGETVERSIONPROC) GetProcAddress(hinstShell, "DllGetVersion"); 织梦好,好织梦 
 内容来自dedecms 
if ( NULL != pProc ) 本文来自织梦 
{ 

内容来自dedecms

DLLVERSIONINFO rInfo = { sizeof(DLLVERSIONINFO) }; dedecms.com 
 

dedecms.com

if ( SUCCEEDED( pProc ( &rInfo ) )) 

织梦好,好织梦

{ 

内容来自dedecms

if ( rInfo.dwMajorVersion > 4 || 织梦好,好织梦 
rInfo.dwMinorVersion >= 71 ) 织梦内容管理系统 
{ 
织梦内容管理系统
bUseOwnerDraw = true; 织梦内容管理系统 
} 

内容来自dedecms

} 织梦好,好织梦 
} 

内容来自dedecms

} 

内容来自dedecms

bUseOwnerDraw表明是否使用自画菜单项. 如果为真, 我们插入自画菜单项(看一下设置mii.fType 的那行代码). 如果为假, 我们添加位图菜单项并告诉菜单显示的位图句柄. 代码使用InsertMenuItem()API 来添加该项; 使用旧的InsertMenu()API 需要你再调用ModifyMenu()来改变为自画菜单. 织梦内容管理系统

MENUITEMINFO mii; 

copyright dedecms

 dedecms.com 
mii.cbSize = sizeof(MENUITEMINFO); 

copyright dedecms

mii.fMask= MIIM_ID | MIIM_TYPE; 
织梦内容管理系统
mii.fType= bUseOwnerDraw ? MFT_OWNERDRAW : MFT_BITMAP; dedecms.com 
mii.wID= uidCmdFirst; 本文来自织梦 
 织梦内容管理系统 
if ( !bUseOwnerDraw ) 本文来自织梦 
{ dedecms.com 
// 注意: 这会将整幅位图放入菜单. 
织梦内容管理系统
mii.dwTypeData = (LPTSTR) m_bmp.GetSafeHandle(); 

织梦内容管理系统

} 
织梦好,好织梦
 

织梦好,好织梦

InsertMenuItem ( hmenu, uIndex, TRUE, &mii ); 

dedecms.com

 
织梦好,好织梦
// 存储菜单项的ID以备而后处理WM_MEASUREITEM/WM_DRAWITEM消息时的检查. 

织梦好,好织梦

m_uOurItemID = uidCmdFirst; 本文来自织梦 
 织梦内容管理系统 
// 告诉Shell我们添加了一项菜单. 

dedecms.com

return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 ); 
织梦好,好织梦
} 本文来自织梦 

我们将菜单项ID 保存在m_uOurItemID中(作者的废话真多) 所以在而后的消息处理中我们将知道此ID. 这不是必要的,因为我们只有一个菜单项, 而如果有多个菜单项这就是必须的. 织梦内容管理系统

在状态栏中显示帮助提示

显示帮助提示与前面的扩展没有什么不同. 浏览器调用GetCommandString()获取帮助字符串. 织梦内容管理系统

#include <atlconv.h>// 供ATL 字符转化宏使用 

本文来自织梦

 dedecms.com 
STDMETHODIMP CBmpCtxMenuExt::GetCommandString ( dedecms.com 
UINT uCmd, UINT uFlags, UINT* puReserved, dedecms.com 
LPSTR pszName, UINT cchMax ) 
dedecms.com
{ 织梦好,好织梦 
static LPCTSTR szHelpString = _T("Select this thumbnail to view the entire picture."); 

内容来自dedecms

 
dedecms.com
USES_CONVERSION; copyright dedecms 
 本文来自织梦 
// 检查 idCmd, 必须为0因为我们只有一个菜单项. 

织梦内容管理系统

if ( 0 != uCmd ) 

内容来自dedecms

return E_INVALIDARG; dedecms.com 
 本文来自织梦 
// 如果浏览器索取帮助字符串, 赋值我们的字符串到提供的缓冲区中. 织梦内容管理系统 
if ( uFlags & GCS_HELPTEXT ) 本文来自织梦 
{ 织梦好,好织梦 
if ( uFlags & GCS_UNICODE ) 

本文来自织梦

{ 

内容来自dedecms

// 我们需要将 pszName 变为Unicode 字符串, 并使用Unicode 字符串拷贝 API. 
dedecms.com
lstrcpynW ( (LPWSTR) pszName, T2CW(szHelpString), cchMax ); 织梦内容管理系统 
} 

copyright dedecms

else 内容来自dedecms 
{ 内容来自dedecms 
// 使用 ANSI 字符串拷贝 API 返回帮助字符串. 
本文来自织梦
lstrcpynA ( pszName, T2CA(szHelpString), cchMax ); 

copyright dedecms

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

内容来自dedecms

执行用户选择

IContextMenu最后一个方法为InvokeCommand(). 当用户点击我们添加的菜单时该方法被调用. 该扩展调用ShellExecute()来打开位图文件.

本文来自织梦

STDMETHODIMP CBmpCtxMenuExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pInfo ) copyright dedecms 
{ 
本文来自织梦
// 如果lpVerb 指向字符串, 忽略此次调用,退出 
本文来自织梦
if ( 0 != HIWORD( pInfo->lpVerb )) 
织梦内容管理系统
return E_INVALIDARG; dedecms.com 
 织梦好,好织梦 
// 命令 ID 必须为0因为我们只添加了一个菜单项. 织梦内容管理系统 
if ( 0 != LOWORD( pInfo->lpVerb )) 
织梦内容管理系统
return E_INVALIDARG; 内容来自dedecms 
 
本文来自织梦
// 使用默认程序打开位图. 

本文来自织梦

ShellExecute ( pInfo->hwnd, _T("open"), m_szFile, NULL, NULL, SW_SHOWNORMAL ); 内容来自dedecms 
 

本文来自织梦

return S_OK; 

织梦内容管理系统

} 
织梦好,好织梦

自画菜单项

OK, 我敢打赌你已经烦透了上面的代码. 新鲜玩意来了! IContextMenu2IContextMenu3添加的两个方法如下所示. 它们仅调用另一个帮助函数, 而帮助函数又调用了一个消息处理器. 这是我所设计的一种方法以适用各种不同版本的消息处理器(分别为IContextMenu2IContextMenu3). HandleMenuMsg2()LRESULT*参数有点奇怪,我在注释中有说明.

织梦内容管理系统

STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg ( UINT uMsg, WPARAM wParam, LPARAM lParam ) dedecms.com 
{ 织梦内容管理系统 
AFX_MANAGE_STATE(AfxGetStaticModuleState()); 
dedecms.com
 copyright dedecms 
// res 只是个虚设的LRESULT 变量. 它并没被真的使用(IContextMenu2::HandleMenuMsg() copyright dedecms 
// 没有一种提供返回值的方法), 这只是为了使MenuMessageHandler() 的调用能够统一,而不论它是被哪个接口调用 内容来自dedecms 
// (IContextMenu2 or 3). 

内容来自dedecms

 
织梦内容管理系统
LRESULT res; 织梦内容管理系统 
 
织梦内容管理系统
return MenuMessageHandler ( uMsg, wParam, lParam, &res ); 织梦内容管理系统 
} copyright dedecms 
 dedecms.com 
STDMETHODIMP CBmpCtxMenuExt::HandleMenuMsg2 ( UINT uMsg, WPARAM wParam, LPARAM lParam, 
织梦好,好织梦
LRESULT* pResult ) 

织梦内容管理系统

{ 

copyright dedecms

AFX_MANAGE_STATE(AfxGetStaticModuleState()); 织梦内容管理系统 
 
织梦好,好织梦
// 对于不需要返回值的消息, pResult 为 NULL. 这正是微软的意思,因为它要求在使用该值之前检查一下 
本文来自织梦
// pResult 是否可用. 你可能会想一个指向”返回值”的指针总是有效的,但是不! 
copyright dedecms
// 如果其为NULL, 我就创建一个虚设的 LRESULT 变量, 所以在MenuMessageHandler()中的代码 

织梦内容管理系统

// 总有一个有效的 pResult 指针. dedecms.com 
 dedecms.com 
if ( NULL == pResult ) 

内容来自dedecms

{ 织梦内容管理系统 
LRESULT res; 
copyright dedecms
return MenuMessageHandler ( uMsg, wParam, lParam, &res ); copyright dedecms 
} copyright dedecms 
else 

copyright dedecms

{ 

dedecms.com

return MenuMessageHandler ( uMsg, wParam, lParam, pResult ); 
本文来自织梦
} 本文来自织梦 
} 

织梦好,好织梦

MenuMessageHandler()分派WM_MEASUREITEMWM_DRAWITEM消息给的消息处理函数. copyright dedecms

STDMETHODIMP CBmpCtxMenuExt::MenuMessageHandler ( UINT uMsg, WPARAM wParam, 织梦内容管理系统 
LPARAM lParam, LRESULT* pResult ) 
织梦内容管理系统
{ 
内容来自dedecms
switch ( uMsg ) copyright dedecms 
{ dedecms.com 
case WM_MEASUREITEM: 织梦好,好织梦 
return OnMeasureItem ( (MEASUREITEMSTRUCT*) lParam, pResult ); 内容来自dedecms 
break; copyright dedecms 
 织梦好,好织梦 
case WM_DRAWITEM: 

织梦内容管理系统

return OnDrawItem ( (DRAWITEMSTRUCT*) lParam, pResult ); 

织梦内容管理系统

break; 
本文来自织梦
} copyright dedecms 
 本文来自织梦 
return S_OK; dedecms.com 
} 
织梦好,好织梦

如我以前所述, 文档说Shell让我们的扩展处理WM_INITMENUPOPUPWM_MENUCHAR 消息, 但我始终没检测到这些消息. 织梦内容管理系统

处理WM_MEASUREITEM消息

Shell 发送WM_MEASUREITEM消息给我们的扩展以索取菜单大小数据. 我们先要检查是否是我们添加的菜单项所激发的调用. 如果测试通过, 我们获取位图的大小, 并计算整个菜单项的大小. 内容来自dedecms

先是位图大小: dedecms.com

STDMETHODIMP CBmpCtxMenuExt::OnMeasureItem ( MEASUREITEMSTRUCT* pmis, LRESULT* pResult ) 

dedecms.com

{ copyright dedecms 
BITMAP bm; 本文来自织梦 
LONGlThumbWidth; copyright dedecms 
LONGlThumbHeight; 

织梦好,好织梦

 copyright dedecms 
// 检查是否是我们添加的菜单项的激发的调用. copyright dedecms 
if ( m_uOurItemID != pmis->itemID ) 内容来自dedecms 
return S_OK; 本文来自织梦 
 织梦好,好织梦 
m_bmp.GetBitmap ( &bm ); 织梦好,好织梦 
 copyright dedecms 
m_lBmpWidth = bm.bmWidth; 
织梦内容管理系统
m_lBmpHeight = bm.bmHeight; 
织梦内容管理系统

接着, 我们计算缩略图的大小, 并据此计算整个菜单项的大小. 如果位图小于缩略图的最大值(64x64象素) 那么就按原样输出. 否则, 就缩放成64x64. 这可能扭曲位图的显示, 要使位图更好看留给你去练习. 织梦好,好织梦

// 计算位图缩略图大小. 

内容来自dedecms

lThumbWidth = (m_lBmpWidth <= m_lMaxThumbnailSize) ? m_lBmpWidth :  织梦内容管理系统 
m_lMaxThumbnailSize; 织梦好,好织梦 
 本文来自织梦 
lThumbHeight = (m_lBmpHeight <= m_lMaxThumbnailSize) ? m_lBmpHeight : 

dedecms.com

m_lMaxThumbnailSize; 织梦好,好织梦 
 织梦好,好织梦 
// 计算菜单项的大小, 即缩略图大小 + 边框的大小+ 空白间距 本文来自织梦 
m_lItemWidth = lThumbWidth + m_lTotalBorderSpace; 内容来自dedecms 
m_lItemHeight = lThumbHeight + m_lTotalBorderSpace; 织梦好,好织梦 

现在我们有了菜单项的大小, 我们将尺寸大小存回MENUITEMSTRUCT. 浏览器将为我们的菜单项保留足够的空间.

织梦好,好织梦

// 将菜单项大小存回 MEASUREITEMSTRUCT. dedecms.com 
pmis->itemWidth = m_lItemWidth; 织梦内容管理系统 
pmis->itemHeight = m_lItemHeight; 
本文来自织梦
 织梦内容管理系统 
*pResult = TRUE;// 我们处理了消息 
内容来自dedecms
 织梦好,好织梦 
return S_OK; 

dedecms.com

} 织梦内容管理系统 

处理WM_DRAWITEM消息

当我们接收到WM_DRAWITEM消息, 浏览器会要求我们来画出菜单项. 我们先计算缩略图周围3D边框的RECT. 该RECT不必与菜单项所占的RECT相同, 因为菜单可能会宽于我们在WM_MEASUREITEM消息处理器中所设置的值. dedecms.com

STDMETHODIMP CBmpCtxMenuExt::OnDrawItem ( DRAWITEMSTRUCT* pdis, LRESULT* pResult ) copyright dedecms 
{ 本文来自织梦 
CDCdcBmpSrc; dedecms.com 
CDC*pdcMenu = CDC::FromHandle ( pdis->hDC ); 
织梦内容管理系统
CRect rcItem ( pdis->rcItem );// 我们的菜单项的RECT 内容来自dedecms 
CRect rcDraw;// 我们作图的RECT 

copyright dedecms

 

dedecms.com

// 检查是否是我们的菜单项激发的调用. 内容来自dedecms 
if ( m_uOurItemID != pdis->itemID ) 
织梦内容管理系统
return S_OK; 

dedecms.com

 内容来自dedecms 
// rcDraw 首先被设为我们在WM_MEASUREITEM消息中设置的 RECT. 织梦好,好织梦 
// 该矩形将被缩小. 织梦内容管理系统 
rcDraw.left = (rcItem.right + rcItem.left - m_lItemWidth) / 2; 织梦内容管理系统 
rcDraw.top = (rcItem.top + rcItem.bottom - m_lItemHeight) / 2; copyright dedecms 
rcDraw.right = rcDraw.left + m_lItemWidth; 本文来自织梦 
rcDraw.bottom = rcDraw.top + m_lItemHeight; 本文来自织梦 
 
本文来自织梦
// 缩小 rcDraw 以适应缩略图周围的间隔空间. 织梦好,好织梦 
 
dedecms.com
rcDraw.DeflateRect ( m_lMenuItemSpacing, m_lMenuItemSpacing ); 织梦内容管理系统 

第一个作画步骤是画出菜单项背景. DRAWITEMSTRUCT 的成员变量itemState表明我们的菜单项是否被选中. 下面的代码决定使用的背景色.

本文来自织梦

// 填充菜单项的背景色. 

dedecms.com

if ( pdis->itemState & ODS_SELECTED ) 织梦好,好织梦 
pdcMenu->FillSolidRect ( rcItem, GetSysColor ( COLOR_HIGHLIGHT )); 内容来自dedecms 
else dedecms.com 
pdcMenu->FillSolidRect ( rcItem, GetSysColor ( COLOR_3DFACE )); 
内容来自dedecms

接着, 画出下沉态的边框使得缩略图看上去嵌在菜单中.

织梦好,好织梦

// 画出下沉的3D边框. 
内容来自dedecms
for ( int i = 1; i <= m_l3DBorderWidth; i++ ) 
织梦好,好织梦
{ dedecms.com 
pdcMenu->Draw3dRect ( rcDraw, GetSysColor ( COLOR_3DDKSHADOW ), 
本文来自织梦
GetSysColor ( COLOR_3DHILIGHT )); 

copyright dedecms

 
内容来自dedecms
rcDraw.DeflateRect ( 1, 1 ); 
内容来自dedecms
} 
织梦好,好织梦

最后一步画出缩略图本身. 我简单地使用StretchBlt()调用来完成. 效果不是很好, 但我的目的是使代码尽量简单. 内容来自dedecms

// 创建 DC 并将位图选进DC中 copyright dedecms 
dcBmpSrc.CreateCompatibleDC ( &dc ); 
织梦好,好织梦
dcBmpSrc.SelectObject ( &m_bmp ); copyright dedecms 
 copyright dedecms 
// 在DC上画出位图. 内容来自dedecms 
pdcMenu->StretchBlt ( rcDraw.left, rcDraw.top, rcDraw.Width(), rcDraw.Height(), 

织梦好,好织梦

&dcBmpSrc, 0, 0, m_lBmpWidth, m_lBmpHeight, SRCCOPY ); 织梦好,好织梦 
 
织梦内容管理系统
*pResult = TRUE;// 我们处理了消息 

内容来自dedecms

return S_OK; 
织梦好,好织梦
} 

dedecms.com

注意在一个实际的扩展中, 最好使用防闪烁的作图类, 这样当你的鼠标在其上移动时,菜单项不会闪烁t. copyright dedecms

下面是这些菜单的一些快照! 第一个是自画菜单, 未选态和选择态. dedecms.com

 [Owner-drawn menu - 6K]  [Owner-drawn menu, selected - 6K] 织梦好,好织梦

下面是在shell 版本4.00时图象的样子. 由于选择态时的高亮反转了所有的颜色使其很难看.

织梦内容管理系统

 [Bitmap menu item - 4K]  [Bitmap menu item, selected - 4K]

内容来自dedecms

注册Shell扩展

注册我们的位图快速浏览工具与以前我们注册上下文菜单扩展相同. 下面是所需的RGS 脚本文件:

dedecms.com

HKCR 本文来自织梦 
{ 织梦内容管理系统 
NoRemove Paint.Picture copyright dedecms 
{ 

织梦好,好织梦

NoRemove ShellEx 
本文来自织梦
{ dedecms.com 
NoRemove ContextMenuHandlers 

织梦内容管理系统

{ 内容来自dedecms 
BitmapPreview = s '{D6F469CD-3DC7-408F-BB5F-74A1CA2647C9}' 
织梦好,好织梦
} 织梦内容管理系统 
} 织梦好,好织梦 
} 

织梦内容管理系统

} 织梦内容管理系统 

注意在这硬编码了"Paint.Picture" 文件类型. 如果你不是使用画笔作为你的BMP默认浏览器, 你需要改变"Paint.Picture" 为存储在HKCR\.bmp 键的默认值. 你应该在DllRegisterServer()做这些注册工作, 这样你可以检查"Paint.Picture" 是不是正确的键. 在第一节 我讲的更多. copyright dedecms

扩展2 – 处理文件夹窗口背景的右击事件

在shell版本4.71 或更高中, 你可以修改右击桌面或任一浏览器窗口背景时显示的上下文菜单. 编程这种扩展类似于其它上下文菜单扩展. 但有两个重要区别: copyright dedecms

4.IShellExtInit::Initialize()的参数的使用不同.

copyright dedecms

5.扩展在不同的注册键下注册 内容来自dedecms

我不会再重复扩展的建立步骤. 如果你想看全部的处理请看示例工程代码. dedecms.com

IShellExtInit::Initialize()中的不同

Initialize()有个pidlFolder参数, 直到目前, 我们都一直忽略其因为它总为NULL. 现在该参数有用了! 它是右击的浏览器窗口的文件夹的PIDL. 而第二个参数IDataObject* NULL, 因为在这并没有所选择的文件.

本文来自织梦

以下是Initialize()的实现:

copyright dedecms

STDMETHODIMP CBkgndCtxMenuExt::Initialize ( 

本文来自织梦

LPCITEMIDLIST pidlFolder, 织梦内容管理系统 
LPDATAOBJECTpDO, 本文来自织梦 
HKEYhkeyProgID ) 本文来自织梦 
{ 
织梦好,好织梦
// pidlFolder是右击的浏览器窗口的文件夹的 PIDL. 而第二个参数 IDataObject* 为NULL,  dedecms.com 
// 因为在这并没有所选择的文件. 

织梦好,好织梦

// 我们使用SHGetPathFromIDList() API来获得路径名 
织梦好,好织梦
 
dedecms.com
return SHGetPathFromIDList ( pidlFolder, m_szDirClickedIn ) ? S_OK : E_INVALIDARG; 

copyright dedecms

} dedecms.com 

SHGetPathFromIDList()函数返回文件夹的全路径并将之保存已备后用. 它返回一个BOOL表明成功与否.

织梦好,好织梦

注册上的不同

该扩展注册在一个不同的键下, 即HKCR\Directory\Background\ShellEx\ContextMenuHandlers. 下面是注册的RGS 脚本:

copyright dedecms

HKCR copyright dedecms 
{ 织梦好,好织梦 
NoRemove Directory 
织梦内容管理系统
{ 织梦内容管理系统 
NoRemove Background 内容来自dedecms 
{ 织梦好,好织梦 
NoRemove ShellEx 内容来自dedecms 
{ 内容来自dedecms 
NoRemove ContextMenuHandlers 织梦内容管理系统 
{ 

织梦内容管理系统

ForceRemove SimpleBkgndExtension = s '{9E5E1445-6CEA-4761-8E45-AA19F654571E}' 织梦内容管理系统 
} 织梦内容管理系统 
} 

本文来自织梦

} 织梦好,好织梦 
} 
本文来自织梦
} 本文来自织梦 

除了这两个区别之外, 该扩展跟其它上下文菜单扩展一样工作. IContextMenu::QueryContextMenu()里有一点要注意.uIndex参数似乎总是-1 (0xFFFFFFFF). 传递-1 给InsertMenu()为索引值意味着菜单项添加在菜单底部. 但是, 如果递增uIndex, 它会溢出到0, 意思是如果你传送uIndexInsertMenu(), 第二个菜单项将出现在菜单顶部. 检查一下例子工程的代码QueryContextMenu()看一下如何正确的放置添加的菜单项. 织梦好,好织梦

下面是所修改的上下文菜单的样子, 在底部添加了两个菜单项. 注意IMHO, 添加菜单项到菜单底部还有个问题. 当用户选择属性时习惯选中最后一项. 当我们的菜单项添加在其之后, 我们会破坏用户的习惯, 而导致失败和烦人的EMail. ;)

内容来自dedecms

 [modified context menu - 22K] 织梦好,好织梦

你可能要使你的菜单项突出显示, 但你这样会破坏用户的使用习惯. 你会使用户的注意力分散去找正确的菜单项. 所以慎重使用该类型的扩展.

织梦好,好织梦

待续...?