Windows 外壳的上下文菜单给使用者带来了很多方便之处,若能把此项功能加入到自己开发的项目中,肯定会为产品增色不少。下面,我就向大家介绍一种用MFC(微软基础类)实现Windows 外壳上下文菜单的方法。
一、实现过程概述
Windows 外壳的上下文菜单属于Windows外壳扩展操作的一种,若想使用外壳扩展操作(例如:属性页、上下文菜单、拖拽操作)就必须首先对外壳扩展操作进行初始化,我们可通过调用IshellExtInit接口来完成此项工作,初始化完毕后我们便可调用IContextMenu接口来完成Windows 外壳的上下文菜单。
二、相关背景知识简介
由于我们的编码工作是基于COM(组件对象模型)基础上的,所以还应具备一些
编写COM程序的基本知识,编写COM程序可用多种语言、多种方法,这里只简要介绍使用VC和MFC编写COM程序的方法。关于其他方法,有兴趣的读者可参阅一些相关资料。
虽然MFC不支持直接使用COM,但是MFC的结构提供了特殊的技术支持,它可以使加入COM功能变得非常简单。在MFC中类CCmdTarget为Iunknown接口提供了标准实现,如果对象从CCmdTarget基类继承,则该对象可通过MFC类COleObjectFactory来自动创建,COleObjectFactory通过DLL入口点DllGetClassObject调用。如果COM类不是从CCmdTarget为基类的类中继承而来,就必须在函数DllGetClassObject中加入特殊的对象创建代码以创建对象的实例。在使用CCmdTarget作为基类后,我们便可调用一系列的支持宏来轻松实现COM对象。
对MFC实现COM对象的过程有了一定了解后,我们就来具体谈一谈程序中所要用到的两个接口IContextMenu和IshellExtInit(详细信息请查阅微软相关文档):
1 IContextMenu的方法:
1) IContextMenu::GetCommandString
格式:HRESULT GetCommandString(
UINT idCmd,
UINT uFlags,
UINT *pwReserved,
LPSTR pszName,
UINT cchMax
);
参数:uFlags 指定的信息标志。
pwReserved 保留字。必须为NULL。
pszName 以空结尾的字符的串地址。
cchMax 以空结尾的字符的最大长度。
作用:得到上下文菜单选项的命令行和帮助文字。
返回值:成功返回NOERROR,否则返回一个OLE错误值。
2) IContextMenu::InvokeCommand
格式:HRESULT InvokeCommand( LPCMINVOKECOMMANDINFO lpici );
参数: lpici 上下文菜单选项的命令信息结构。
作用:得到上下文菜单选项的命令信息。
返回值:成功返回NOERROR,否则返回一个OLE错误值。
3) IContextMenu::QueryContextMenu
格式:HRESULT QueryContextMenu(
HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags
);
作用:可加入菜单选项到指定菜单。
参数:hmenu 指定菜单句柄。
indexMenu 插入菜单选项的位置索引(以0为基准)。
idCmdFirst 菜单选项可被指定的最小命令编号。
idCmdLast 菜单选项可被指定的最大命令编号。
uFlags 菜单选项的风格标志。
返回值:成功返回添加菜单项数。
三、IShellExtInit的方法:
1)IShellExtInit::Initialize
格式:HRESULT Initialize(
LPCITEMIDLIST pidlFolder,
LPDATAOBJECT lpdobj,
HKEY hkeyProgID
);
作用:初始化外壳扩展操作(例如:属性页、上下文菜单、拖拽操作)。
参数:pidlFolder 一个ITEMIDLIST结构的地址。
lpdobj 一个能被用的IDataObject接口地址。
hkeyProgID 文件对象或文件夹类型的注册号。
返回值:成功返回NOERROR,否则返回一个OLE错误值。
1 主要源程序:
1) ContextMenuSample.h文件
#if !defined(AFX_CONTEXTMENUSAMPLE_H__5F5F7E71_ADC6_11D3_AF23_AC9F5AA57808__INCLUDED_)
#define AFX_CONTEXTMENUSAMPLE_H__5F5F7E71_ADC6_11D3_AF23_AC9F5AA57808__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// ContextMenuSample.h : header file
//
#include <shlobj.h>
/////////////////////////////////////////////////////////////////////////////
// CContextMenuSample command target
class CContextMenuSample : public CCmdTarget
{
DECLARE_DYNCREATE(CContextMenuSample)
CContextMenuSample(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CContextMenuSample)
public:
virtual void OnFinalRelease();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CContextMenuSample();
// Generated message map functions
//{{AFX_MSG(CContextMenuSample)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// Generated OLE dispatch map functions
//{{AFX_DISPATCH(CContextMenuSample)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
DECLARE_OLECREATE(CContextMenuSample)
//支持COM接口映射
DECLARE_INTERFACE_MAP()
//BEGIN_INTERFACE_PART(类名,接口名)
//开始定义接口类
//不必要加入IUnknown接口方法,IUnknown接口方法可自动加入类中
BEGIN_INTERFACE_PART(CMS,IContextMenu)
STDMETHOD(QueryContextMenu)(HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags);
STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici);
STDMETHOD(GetCommandString)(UINT idCmd,
UINT uType,
UINT * pwReserved,
LPSTR pszName,
UINT cchMax);
END_INTERFACE_PART(CMS)
//
//BEGIN_INTERFACE_PART(类名,接口名)
//开始定义接口类
BEGIN_INTERFACE_PART(SEI,IShellExtInit)
STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder,
LPDATAOBJECT lpdobj,
HKEY hkeyProgID);
END_INTERFACE_PART(SEI)
};
/////////////////////////////////////////////////////////////////////////////
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_CONTEXTMENUSAMPLE_H__5F5F7E71_ADC6_11D3_AF23_AC9F5AA57808__INCLUDED_)
2. ContextMenuSample.cpp文件
// ContextMenuSample.cpp : implementation file
//
#include "stdafx.h"
#include "CMS.h"
#include "ContextMenuSample.h"
#include "Commacros.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//
#define ResultFromShort(i) ResultFromScode(MAKE_SCODE(SEVERITY_SUCCESS, 0, (USHORT)(i)))
/////////////////////////////////////////////////////////////////////////////
// CContextMenuSample
IMPLEMENT_DYNCREATE(CContextMenuSample, CCmdTarget)
CContextMenuSample::CContextMenuSample()
{
EnableAutomation();
}
CContextMenuSample::~CContextMenuSample()
{
}
void CContextMenuSample::OnFinalRelease()
{
// When the last reference for an automation object is released
// OnFinalRelease is called. The base class will automatically
// deletes the object. Add additional cleanup required for your
// object before calling the base class.
CCmdTarget::OnFinalRelease();
}
BEGIN_MESSAGE_MAP(CContextMenuSample, CCmdTarget)
//{{AFX_MSG_MAP(CContextMenuSample)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
BEGIN_DISPATCH_MAP(CContextMenuSample, CCmdTarget)
//{{AFX_DISPATCH_MAP(CContextMenuSample)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_DISPATCH_MAP
END_DISPATCH_MAP()
// Note: we add support for IID_IContextMenuSample to support typesafe binding
// from VBA. This IID must match the GUID that is attached to the
// dispinterface in the .ODL file.
//
// {5F5F7E70-ADC6-11D3-AF23-AC9F5AA57808}
static const IID IID_IContextMenuSample =
{ 0x5f5f7e70, 0xadc6, 0x11d3, { 0xaf, 0x23, 0xac, 0x9f, 0x5a, 0xa5, 0x78, 0x8 } };
//IMPLEMENT_OLECREATE宏,可自动实现类对象实例化
IMPLEMENT_OLECREATE(CContextMenuSample,"ContextMenuSample", 0x5f5f7e70, 0xadc6, 0x11d3, 0xaf, 0x23, 0xac, 0x9f, 0x5a, 0xa5, 0x78, 0x8 )
//实现接口映射
BEGIN_INTERFACE_MAP(CContextMenuSample, CCmdTarget)
//IContextMenu 接口
INTERFACE_PART(CContextMenuSample, IID_IContextMenu, CMS)
//IShellExtInit 接口
INTERFACE_PART(CContextMenuSample, IID_IShellExtInit, SEI)
END_INTERFACE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CContextMenuSample message handlers
//IShellExtInit接口的实现
//IMPLEMENT_IUNKNOWN实现了IUnknown接口的基本方法,请参阅Commacros.h文件
IMPLEMENT_IUNKNOWN(CContextMenuSample,SEI)
STDMETHODIMP CContextMenuSample::XSEI::Initialize(LPCITEMIDLIST pidlFolder,
LPDATAOBJECT lpdobj,
HKEY hkeyProgID)
{
//METHOD_PROLOGUE 宏用于建立名叫pThis的局部变量,该变量是指向接口函数表的指针。
//该宏必须置于每个接口实现之前。
METHOD_PROLOGUE(CContextMenuSample,SEI);
return (HRESULT)NOERROR;
}
//IMPLEMENT_IUNKNOWN实现了IUnknown接口的基本方法,请参阅Commacros.h文件
IMPLEMENT_IUNKNOWN(CContextMenuSample,CMS)
STDMETHODIMP CContextMenuSample::XCMS::QueryContextMenu(
HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags)
{
METHOD_PROLOGUE(CContextMenuSample,CMS);
UINT idCmd = idCmdFirst;
//在上下文菜单中添加菜单选项
InsertMenu(hmenu,indexMenu,MF_STRING|MF_BYPOSITION,idCmd++,"CMS Sample");
//得到位图
HBITMAP hBmp = LoadBitmap(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDB_BMP));
//在菜单旁设置位图
SetMenuItemBitmaps(hmenu, indexMenu, MF_BYPOSITION, hBmp, hBmp);
return ResultFromShort(idCmd - idCmdFirst);
}
STDMETHODIMP CContextMenuSample::XCMS::InvokeCommand(LPCMINVOKECOMMANDINFO lpici)
{
METHOD_PROLOGUE(CContextMenuSample,CMS);
//检测菜单项是否被选中
if (!HIWORD(lpici->lpVerb))
{
UINT idCmd = LOWORD(lpici->lpVerb);
if(idCmd==0)
AfxMessageBox("ContextMenu Sample !");
}
return (HRESULT)NOERROR;
}
STDMETHODIMP CContextMenuSample::XCMS::GetCommandString(
UINT idCmd,
UINT uType,
UINT * pwReserved,
LPSTR pszName,
UINT cchMax)
{
METHOD_PROLOGUE(CContextMenuSample,CMS);
return (HRESULT)NOERROR;
}
四、测试方法:
1.将生成的cms.dll拷入C:\windows\system文件夹下。
2.双击CMS文件夹下的CMS.reg文件,注册完毕后会显示对话框。
3.选取任一文件夹,按右键弹出上下文菜单后,点击上下文菜单上的CMS Sample选项即可。
有兴趣的朋友,可到我们的主页(http://cave.163.net)下载源程序cmsdoc.zip。
参考文献:
1. 《Visual C++ 5 ActiveX 编程指南》,清华大学出版社,1998
2. 《基于组件的应用程序设计》,北京大学出版社,1999
|