当前位置: 首页 > 学习 > 电脑学习 > 程序设计 > C++ > 系统控制 > 正文

用C++ Builder创建上下文菜单扩展处理器

http://www.zk168.com.cn  招考学习网 2006-4-11 6:09:20
-----------------------------------------------------------[交流]-[打印]-[发送]-[收藏]--
当用户右击一个shell对象时,shell会显示它的上下文菜单。文件系统对象有大量的标准菜单项,如"剪切"和"拷贝",这些都是缺省的菜单项。如果对象是一个文件,是文件类的成员,就能够在注册表里指定附加的菜单项。Shell检查注册表,看看文件类型是否与一些上下文菜单handler相关联,如果是,shell会咨询这些handler是否添加额外的菜单项。

    上下文菜单handler是一种shell扩展handler,它添加命令到已有的上下文菜单中。上下文菜单handler都与特定的文件类相关联,并且在显示这类文件的成员的上下文菜单时调用。通过实现和注册这样一个handler,能够动态地添加菜单项到对象的上下文菜单上,从而为特殊的对象定制菜单。

上下文菜单Handler的工作原理

    作为一种shell扩展handler,上下文菜单handler同所有其它handler一样, 是进程内COM 对象,即对象作为动态连接库 (DLL)实现。除了IUnknown接口外,上下文菜单还必须导出IShellExtInit和IContextMenu接口,作为选择,上下文菜单也能导出IContextMenu2和IContextMenu3,这些接口可以实现自画菜单项。

    IShellExtInit接口仅仅被shell用来初始化handler,主要的操作通过handler的IContextMenu接口进行。Shell首先调用IContextMenu::QueryContextMenu,传送一个HMENU句柄,这个方法用它来增加上下文菜单。如果用户亮选了这些新添加的某个命令项, IContextMenu::GetCommandString将被调用,以取得这条菜单的帮助信息,把它显示在资源管理器的状态条上。如果用户单击了handler的条目,shell调用IContextMenu::InvokeCommand,从而handler能够执行合适的操作。

实现IContextMenu接口

1、实现QueryContextMenu方法

    Shell通过调用IContextMenu::QueryContextMenu,允许handler把它的菜单项添加到菜单中。QueryContextMenu共有5个参数,各参数作用如下:

1) Hmenu:HMENU类型,表示上下文菜单的句柄。
2) IndexMenu:第一个被添加的菜单索引。
3) IdCmdFirst:添加的菜单ID初值。
4) idCmdLast:添加的菜单ID最大值。
5) uFlags:与上下文菜单相关的状态标志,共有3种,如下表:

CMF_DEFAULTONLY 用户选择了缺省的命令,通常是通过双击对象产生。QueryContextMenu 在把控制返回给shell前不应该修改菜单。
CMF_NODEFAULT 菜单没有缺省的条目,这个方法应该把它的命令加到菜单中。
CMF_NORMAL 上下文菜单将被正常显示,这个方法应该把它的命令加到菜单中。

    必须注意的是,任何添加的菜单项的ID必须落在idCmdFirst和idCmdLast两个参数中间,通常,添加的第一个菜单项ID设为idCmdFirst,以后每添加一个菜单项,就把ID加1,这样,即使shell调用了不止一个handler,也可以确保菜单项的ID不超过idCmdLast和可能的ID最大值。
在ID和idCmdFirst之间,菜单项ID的command offset(命令偏移)是不同的,应该保存handler添加到上下文菜单中的每个菜单项的offset,因为如果shell按顺序调用GetCommandString或者InvokeCommand,可以使用它来鉴别菜单项的ID。

    还应该为每一个添加的命令赋予一个verb。Verb是语言独立的字符串,当调用InvokeCommand时,常常用verb来代替偏移以鉴别命令。
QueryContextMenu 方法使用InsertMenu或InsertMenuItem 添加新的菜单项,然后返回一个严格设置为SEVERITY_SUCCESS的HRESULT值,把它的值设置为被分配的最大的命令ID。例如,假如idCmdFirst是5,添加了3个菜单项,ID分别是5,7,8,则返回值应该是MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 - 5 + 1)。

以下是一个QueryContextMenu实例:

HRESULT __stdcall TAddContextMenuImpl::QueryContextMenu(HMENU hmenu,
         UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
   if(!(CMF_DEFAULTONLY & uFlags))
   {
      InsertMenu(hmenu, indexMenu, MF_STRING | MF_BYPOSITION,idCmdFirst,
      _T("选择打开方式..."));
      return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 1);
   }
   return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(0));
}

2、实现GetCommandString 方法

    如果用户高亮了一个handler添加的菜单项,shell将调用handler的GetCommandString方法。这个方法需要传递菜单项的偏移值(ID)、指定信息类型的标志、一个预留的参数、一个字符串缓冲区以及缓冲区的大小。

    一般,这个方法可以不用处理,以下示例程序直接返回S_OK。

HRESULT __stdcall TAddContextMenuImpl::GetCommandString(UINT idCmd, UINT uFlags,
UINT *pwReserved, LPSTR pszName, UINT cchMax)
{
   return S_OK;
}

3、实现InvokeCommand方法

    当在上下文菜单中选择一个菜单项时,shell就会调用InvokeCommand,告诉handler运行相关联的命令。在Shlobj.h中,参数pici被声明为CMINVOKECOMMANDINFO结构,但实际上,它经常指向CMINVOKECOMMANDINFOEX结构,这个结构是CMINVOKECOMMANDINFO的扩展版本,有几个成员允许传递Unicode字符串。

CMINVOKECOMMANDINFO的成员简介如下:

1) cbSize :结构的大小。

2) fMask :为0,或下列标志的组合。

CMIC_MASK_ASYNCOK 在返回之前等待DDE会话结束
CMIC_MASK_FLAG_NO_UI 当执行命令时,系统防止显示用户接口元素(如错误信息)
CMIC_MASK_HOTKEY dwHotKey 成员有效
CMIC_MASK_ICON hIcon成员有效
CMIC_MASK_NO_CONSOLE 如果上下文菜单handler必须创建新进程,正常情况下将创建一个控制台,设置CMIC_MASK_NO_CONSOLE标志可以禁止创建新的控制台

3) hwnd :拥有上下文菜单窗口的句柄,handler可以使用这个句柄显示自己的信息提示框和对话框。

4) lpVerb :32位值,高位字包含0,低位字是命令的菜单ID偏移。当用户选择一个菜单命令时,Shell用MAKEINTRESOURCE宏产生这个值,如果高位字不是0,那么这个成员指向一个以NULL结尾的字符串,指出命令的语言无关的名称,即上文的verb。典型情况下,当命令被一个应用程序激活时,这个成员是一个字符串。系统提供了下面几个预定义的常数值:

字符串
CMDSTR_NEWFOLDER "NewFolder"
CMDSTR_VIEWDETAILS "ViewDetails"
CMDSTR_VIEWLIST "ViewList"

5) lpParameters :命令传送的参数字符串,对于shell扩展插入的菜单项,这个成员总是NULL。

6) lpDirectory :目录名称,对于shell扩展插入的菜单项,这个成员总是NULL。

7) nShow :显示窗口或启动应用程序时,传递给ShowWindow函数的参数。

8) dwHotKey :分配给被命令激活的应用程序的热键。如果fMask 不是CMIC_MASK_HOTKEY,这个成员被忽略。

9) hIcon :被命令激活的应用程序使用的图标。如果fMask 不是CMIC_MASK_ICON,这个成员被忽略。

    以下示例先打开一个"选择文件"的对话框,然后用所选择的程序打开在资源管理器中被选择的文件。为了简化,假定在资源管理器只选择了一个文件。

HRESULT __stdcall TAddContextMenuImpl::InvokeCommand(LPCMINVOKECOMMANDINFO pici)
{
   if(HIWORD(pici->lpVerb)==0)
   {
      if(LOWORD(pici->lpVerb)==0) // 添加的第一个菜单项
      {
         TOpenDialog *Dlg=new TOpenDialog(NULL);
         Dlg->Title="打开\"";
         Dlg->Title=Dlg->Title+g_szFilePath+"\"";
         Dlg->Options.Clear();
         Dlg->Options << ofFileMustExist << ofPathMustExist << ofNoChangeDir;
         if(Dlg->Execute())
         {
            ShellExecute(pici->hwnd,"open",Dlg->FileName.c_str(),g_szFilePath,NULL,SW_SHOW);          }
         return S_OK;
      }
   }
   return S_FALSE;
}

注册上下文菜单Handler

    上下文菜单与文件类或者文件夹相关联。对于文件类,handler注册在文件类的HKEY_CLASSES_ROOT\ProgID\Shellex\ContextMenuHandlers子键下。在ContextMenuHandlers下创建一个以handler子键,把子键的缺省值设置为handler的CLSID的字符串值,就可以完成注册。

    也能够把handler关联到文件夹,注册的方法与上面类似,不过是在HKEY_CLASSES_ROOT\FolderType\Shellex\ContextMenuHandlers增加子键, 其中的FolderType 是文件夹类型的名称。

    如果一个文件类有上下文菜单与它关联,那么双击一个对象将自动启动缺省的命令,而不会调用handler的QueryContextMenu方法。当对象被双击时,为了指定调用handler的QueryContextMenu方法,必须在handler的CLSID键下创建一个ShellEx\MayChangeDefaultMenu的子键。这样,当与handler关联的对象被双击时,QueryContextMenu 被调用,而且uFlags参数会包含CMF_DEFAULTONLY 标志。

    注意,如果设置了MayChangeDefaultMenu键,当一个关联的项目被双击时,会强制系统载入handler的DLL。如果handler不改变缺省动作,就不应该设置MayChangeDefaultMenu,否则会引起系统不必要地载入这个DLL。仅仅当在可能改变上下文菜单的缺省动作时,才应该在设置上下文菜单handler的这个值。

创建工程

    作为Borland的产品,用C++ Builder创建shell扩展的过程与Delphi有类似之处,但它毕竟是C++语言,所以也有与VC类似之的地方。

    1. 选择File菜单的New菜单项,翻到New Items对话框的ActiveX页,双击ActiveX Library项,创建一个新的COM工程,把工程命名为MyContextMenu。从New Items 对话框的ActiveX页选择COM Object项,将打开COM Server向导。把"COClass"改为AddContextMenu,选择Apartment线程模式。其它不要改写。C++ Builder自动产生一个接口和一个类。默认的类名是TAddContextMenuImpl,采用自动生成的IAddContextMenu接口。我们必须自己添加新的接口IShellExtInit和IContextMenu,如下所示,粗体是添加的内容:

#include <shlobj.h> // 声明IShellExtInit和IContextMenu的头文件
class ATL_NO_VTABLE TAddContextMenuImpl :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<TAddContextMenuImpl, &CLSID_AddContextMenu>,
public IShellExtInit,
public IContextMenu,

public IAddContextMenu
{
   private:
   char g_szFilePath[MAX_PATH];
   public:
   … …
   BEGIN_COM_MAP(TAddContextMenuImpl)
   COM_INTERFACE_ENTRY(IAddContextMenu)
   COM_INTERFACE_ENTRY(IContextMenu) // 导出IContextMenu接口
   COM_INTERFACE_ENTRY(IShellExtInit) // 导出IShellExtInit接口
   END_COM_MAP()
   … …
};

   2. 实现IShellExtInit接口的Initialize方法,在类定义中增加如下内容:

STDMETHOD (Initialize)(LPCITEMIDLIST pidlFolder,LPDATAOBJECT lpdobj,HKEY hkeyProgID);

    Initialize方法的代码如下,从lpdobj对象中取出资源管理器中选择的文件名,程序假定只选择了一个文件。

HRESULT __stdcall TAddContextMenuImpl::
Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT lpdobj, HKEY hkeyProgID)
{
    FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };
    HDROP hDrop;
    if (FAILED(lpdobj->GetData(&fmt, &stg))) return E_FAIL;
    hDrop = (HDROP)GlobalLock(stg.hGlobal);
    if ( hDrop == NULL)
    {
        ReleaseStgMedium(&stg);
        return E_OUTOFMEMORY;
    }
    DragQueryFile(hDrop, 0, g_szFilePath, MAX_PATH);
    GlobalUnlock(stg.hGlobal);
    ReleaseStgMedium(&stg);
    return S_OK;
}

    3. 实现IContextMenu接口的各个方法,内容如上文所示,声明如下:

public:
STDMETHOD (QueryContextMenu)(HMENU hmenu,UINT indexMenu,UINT idCmdFirst,UINT idCmdLast,UINT uFlags);
STDMETHOD (InvokeCommand)(LPCMINVOKECOMMANDINFO pici);
STDMETHOD (GetCommandString)(UINT idCmd,UINT uFlags,UINT *pwReserved,LPSTR pszName,UINT cchMax);

    最后,把工程编译为DLL文件,运行菜单[Run->Register ActiveX Server],把DLL注册。与Delphi和VC相比,C++ Builder似乎有些缺陷。首先,它实现时太过复杂,生成的文件一大堆。最麻烦的是,它无法实现自动注册为shell扩展,它没有VC的rgs文件,像Delphi那样改写UpdateRegistry函数,怎么也不行,好像这个函数没有调用一样。无奈,只好自己动手向注册表添加必须的项目(如图)。但是,C++ Builder给出了3个CLSID,很迷惑人,正确的CLSID应该是类AddContextMenu的,C++ Builder给它命名为CLSID_AddContextMenu。

    注册后,在资源管理器右击任何文件,如readme.txt,都将打开一个选择文件的对话框,然后shell用选择的文件打开readme.txt。完整的程序清单请到《程序员》网站下载,网址是http://www.csdn.net/magazine/。

-----------------------------------------------------------[交流]-[打印]-[发送]-[收藏]--
最新入库:
 
·实质、过程及意义——阿多尔诺“否定的辩证法”探微
·从Ontology的译名之争看哲学术语的翻译原则
·论马克思主义哲学经典的解释——解释学方法及其在马克
·中国哲学当前的核心与周边问题
·和合学与21世纪文化价值和科技
·中国文化的和合精神与21世纪
·宗教之间理当相互宽容
·上半个世纪的自由主义
·殷周至春秋时期神人关系之演进
·大学之道:构建以“三纲八目”为核心的道德修养体系
相关内容:
 
·21世纪以煤和天然气为原料的C1化学
·BOG压缩机在液化石油气基地的应用
·OECD主要国家软件业发展概况
·英美CPA管理模式及其启示
·沙角C电厂事故顺序记录的通道组态分析及整改
·LFCB-102型微波分相差动保护的应用
·沙角C电厂厂用电结线分析
·基于PB6和ORACLE8开发“劳动信息管理系统”
·利用TDC组件实现对WEB页面的交互操作
·TCP/IP在网络中的高效配置
网友点评:
 
会员名称:
密码:匿名 ·注册·忘记密码?
评论内容:
(最多300个字符)
  查看评论
友情提醒:
 1.库中的资料大都来自互联网、网友上传、各类书籍,在录入的过程中难免会出现错误,恳请网
 友来信指正!
 2.如果网友在本库中未能找到所需要的材料,请登陆到我们的论坛《招考学习网》版块!
 3.考友想加入招考学习网的编辑部,请发信到XueXiWang#Gmail.com(#改为@)附带个人简历
 4.如需转载请注明出处及作者,谢谢合作!
 5.如果您有更好的建议或意见请EMAIL:XueXiWang#Gmail.com (#改为@)
 6.凡标题中有注有“[NO]”字样均不含答案且答案整理中.
 7.如本库中转载文章涉及版权等问题,请相关网站或作者在两周内发邮件通知(EMAIL:  XueXiWang#Gmail.com (#改为@))我们,我们接到通知后立即删除该文章及链接!
你问我答 更多>>