当前位置:首页 > c++ > 正文内容

ATL实现windows右键菜单扩展(ContextMenu)

xuwenyan2年前 (2021-04-13)c++918

右键菜单,即用户右击shell对象时弹出的上下文菜单(context menu)。本文记录了如何创建右键菜单的基本过程,跟着步骤一步一步来,即可创建出一个右键菜单工程。

第一步,新建一个ATL工程

Visual Studio—>新建项目—>ATL—>使用默认配置(一直按下一步即可)。

注:如果生成的项目中多了一个以PS为后缀的子项目,可以选择将之移除,并删除下图标识的无用文件。

第二步,修改工程配置

右键工程属性,分别修改以下几项:

修改下图项支持xp系统

修改下图项避免出现缺失dll错误

修改下图项,避免编译后注册(vs如果是非管理员权限启动,编译完会提示regsvr32错误)

第三步,为项目添加一个简单的ATL对象,继承并实现相应的IContextMenu、IShellExtInit的接口方法。

右键项目—>添加(Add)—>类(Class)—>ATL—>ATL Simple Object—>添入下图标识的地方(其它向导会自动生成)—>点击Finish

打开刚才生成的.h文件,并在类继承后加上

public IShellExtInit,public IContextMenu

添加COM类型映射:在宏BEGIN_COM_MAP与宏BEGIN_COM_END之间的列表下手动加上

COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)

然后覆盖并实现IContextMenu接口(QueryInterface,GetCommandString、InvokeCommand)以及IShellExtInit接口(Initialize)系统对这些接口的调用顺序、时刻为:

1:Initialize(用户右键点击某个Shell程序时)
2:QueryContextMenu(1.返回S_OK或其他表示初始化成功的HRESULT时。插入自定义菜单的入口。)
3:GetCommandString(用户光标盘旋(hover)在插入的菜单项时,系统status bar将显示的信息。Vista以后的系统不再有作用,不是实现的重点。)
4:InvokeCommand(用户点击新插入的菜单项时,将会调用这个方法。用户点击菜单项回调的入口。)

第四步,插入菜单

HRESULT STDMETHODCALLTYPE QueryContextMenu(_In_  HMENU hmenu, _In_  UINT indexMenu, _In_  UINT idCmdFirst, _In_  UINT idCmdLast, _In_  UINT uFlags) override {
    if (CMF_DEFAULTONLY & uFlags) {
      return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, USHORT(0));
    }
    UINT uid = idCmdFirst;
    //当你插入菜单的时候,同时递增uid,同时保存uid值到对应菜单的映射。

    //递增uid时,也需要判断一下uid<=idCmdLast
    HBITMAP hbmpDemo = ::LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_SHELL_ICON));

    MENUITEMINFO mii;
    ::memset(&mii, 0, sizeof(MENUITEMINFO));
    mii.cbSize = (UINT)sizeof(MENUITEMINFO);
    mii.fMask = MIIM_STRING | MIIM_ID | MIIM_FTYPE | MIIM_BITMAP;
    mii.fType = MFT_STRING;
    mii.dwTypeData = L"Demo";
    mii.wID = uid++;//id 为[idCmdFirst,idCmdLast]之间某个值
    mii.hbmpItem = hbmpDemo; // xp系统这种方法是有问题的,图标会变色,请看底部解决办法
    ::InsertMenuItem(hmenu, indexMenu++, TRUE, (LPCMENUITEMINFO)&mii);

    //插入菜单完毕
    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, uid - idCmdFirst);
  }

第五步,实现菜单点击

HRESULT STDMETHODCALLTYPE InvokeCommand(_In_  CMINVOKECOMMANDINFO *pici) override {
    //利用pici的lpVerb判断对应哪个自定义菜单项被点击了
    WORD menuId = LOWORD(pici->lpVerb);
    //一般选择switch case的方法来进行判断
    switch (menuId) {
      // 做某个菜单被点击的用户逻辑。   
      // menuId 就是 添加时 mii.wID - idCmdFirst 的值
    }
    return S_OK;
  }

第六步,修改.rgs

修改添加对象时生成的.rgs文件,注意里面的GUID值保持一致

HKCR
{
	ContextMenu.1 = s 'context_menu Class'
	{
		CLSID = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
	}
	ContextMenu = s 'context_menu Class'
	{		
		CurVer = s 'ContextMenu.1'
	}
	NoRemove CLSID
	{
		ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s 'context_menu Class'
		{
			ProgID = s 'ContextMenu.1'
			VersionIndependentProgID = s 'ContextMenu'
			ForceRemove Programmable
			InprocServer32 = s '%MODULE%'
			{
				val ThreadingModel = s 'Apartment'
			}
			TypeLib = s '{EFD0F068-5260-4F4A-8110-A314FF5BB5FB}'
			Version = s '1.0'
		}
	}

	NoRemove *
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
            }
        }
    }
	
	NoRemove lnkfile
	{
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
            }
        }
	}

    NoRemove Directory
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
            }
        }
    }

	NoRemove Folder
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
            }
        }
    }

	NoRemove Drive
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove {6170829F-B85F-4E53-AAFC-74C08A30E572} = s '{6170829F-B85F-4E53-AAFC-74C08A30E572}'
            }
        }
    }
}

第七步,注册、反注册,测试扩展dll

管理员权限运行cmd

运行 (regsvr32 dll路径)实现注册

运行 (regsvr32 dll路径 /u)实现反注册

注意64位系统需要注册64位dll扩展

关于右键菜单所选中文件的获取

既然右键菜单是针对文件或文件夹做什么事,那我们肯定需要获取操作的文件。

HRESULT STDMETHODCALLTYPE Initialize(_In_opt_  PCIDLIST_ABSOLUTE pidlFolder, _In_opt_  IDataObject *pdtobj, _In_opt_  HKEY hkeyProgID) override {
    FORMATETC fmt = { CF_HDROP,NULL,DVASPECT_CONTENT,-1,TYMED_HGLOBAL };
    STGMEDIUM stg = { TYMED_HGLOBAL };
    if (FAILED(pdtobj->GetData(&fmt, &stg)))
      return E_INVALIDARG;

    std::vector<std::wstring> files_;
    HDROP hDrop = (HDROP)::GlobalLock(stg.hGlobal);
    if (hDrop != nullptr) {
      wchar_t szFile[MAX_PATH] = { 0 };
      UINT uFileCount = ::DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
      for (UINT i = 0; i < uFileCount; ++i) {
        ::DragQueryFile(hDrop, i, szFile, MAX_PATH);
        files_.push_back(szFile);
      }

      ::GlobalUnlock(stg.hGlobal);
    }

    ::ReleaseStgMedium(&stg);
    return files_.size() == 0 ? E_INVALIDARG : S_OK;
  }

关于xp图标异常处理

1:图标颜色异常

windows vista及以上系统支持直接设置一个PARGB32格式的 bitmap。但是windows xp 需要使用 HBITMAP_CALLBACK,HBITMAP_CALLBACK 触发 WM_MEARITEM & WM_DRAWITEM,需要实现 IContextMenu3/IContextMenu2 消息 处理以上消息。

插入菜单的代码改为:

MENUITEMINFO mii;
::memset(&mii, 0, sizeof(MENUITEMINFO));
mii.cbSize = (UINT)sizeof(MENUITEMINFO);
mii.fMask = MIIM_STRING | MIIM_ID | MIIM_FTYPE | MIIM_BITMAP;
mii.fType = MFT_STRING;
mii.dwTypeData = L"Demo";
mii.wID = uid++;//id 为[idCmdFirst,idCmdLast]之间某个值
mii.hbmpItem =  ::IsWindowsVistaOrGreater() ? hbmpDemo : HBITMAP_CALLBACK;
::InsertMenuItem(hmenu, indexMenu++, TRUE, (LPCMENUITEMINFO)&mii);

继承IContextMenu3/IContextMenu2

实现IContextMenu3/IContextMenu2的方法HandleMenuMsg/HandleMenuMsg2

HRESULT STDMETHODCALLTYPE HandleMenuMsg(
    /* [annotation][in] */
    _In_  UINT uMsg,
    /* [annotation][in] */
    _In_  WPARAM wParam,
    /* [annotation][in] */
    _In_  LPARAM lParam) {
    LRESULT result = TRUE;
    return HandleMenuMsg2(uMsg, wParam, lParam, &result);
}

HRESULT STDMETHODCALLTYPE HandleMenuMsg2(
    /* [annotation][in] */
    _In_  UINT uMsg,
    /* [annotation][in] */
    _In_  WPARAM wParam,
    /* [annotation][in] */
    _In_  LPARAM lParam,
    /* [annotation][out] */
    _Out_opt_  LRESULT *plResult) {
    switch (uMsg) {
    case WM_MEASUREITEM:
    {
      MEASUREITEMSTRUCT* lpmis = (MEASUREITEMSTRUCT*)lParam;
      if (lpmis == nullptr)
        break;

      lpmis->itemWidth = 16;
      lpmis->itemHeight = 16;

      *plResult = TRUE;
    }
    break;
    case WM_DRAWITEM:
    {
      DRAWITEMSTRUCT* lpdis = (DRAWITEMSTRUCT*)lParam;
      if ((lpdis == nullptr) || (lpdis->CtlType != ODT_MENU))
        return S_OK;

      HICON hicon_item = nullptr;// (HICON)::LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_SHELL_ICON), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
      if (hicon_item == nullptr)
        return S_OK;

      ::DrawIconEx(lpdis->hDC,
        lpdis->rcItem.left,
        lpdis->rcItem.top + (lpdis->rcItem.bottom - lpdis->rcItem.top - 16) / 2,
        hicon_item, 16, 16,
        0, NULL, DI_NORMAL);

      *plResult = TRUE;
    }
    break;
    default:
      break;
    }
    return S_OK;
}

2:图标宽度异常

菜单图标要么前面要么后面,留白很严重,图标看起来偏移了很多。

只需在QueryContextMenu添加完菜单后调用TweakMenu函数即可。

void TweakMenu(HMENU hMenu) {
    MENUINFO MenuInfo = { 0 };
    MenuInfo.cbSize = sizeof(MenuInfo);
    MenuInfo.fMask = MIM_STYLE | MIM_APPLYTOSUBMENUS;
    MenuInfo.dwStyle = MNS_CHECKORBMP;
    ::SetMenuInfo(hMenu, &MenuInfo);
  }
    文章作者:xuwenyan
    版权声明:本文为本站原创文章,转载请注明出处,非常感谢,如版权漏申明或您觉得任何有异议的地方欢迎与本站取得联系。

    扫描二维码推送至手机访问。

    版权声明:本文由艺文笔记发布,如需转载请注明出处。

    本文链接:https://www.xuwenyan.com/archives/1692

    分享给朋友:

    “ATL实现windows右键菜单扩展(ContextMenu)” 的相关文章

    C++指针*为什么靠后会比较好?

    C++指针*为什么靠后会比较好?

    大多数书中和大神的代码里,往往指针的*都是靠变量而不是靠类型的,这主要是为了不造成我们第一眼对变量类型的误解和对指针类型的误解,比如: int* p1,p2 我们一眼看上去是不是通常会觉得p1、p1都是一个int*的指针呢?因为我们通常会误把int*当作一个类型,然而无论int*还是i...

    C++如何获取控制台程序的输出内容?

    C++如何获取控制台程序的输出内容?

    很多工具程序(如ffmpeg)的进度显示往往都是以控制台字符显示的方法,我们可能需要调用这种控制台工具去完成工作,但同时又希望以友好的ui界面去显示当前的工作状态(如进度)。此时我们能想到的就是运行控制台程序,然后以某种方式去获取到控制台程序的输出,然后转换到我们的ui界面上去显示。 有多种...

    解决程序在xp系统总是莫名奇妙的崩溃问题(/Zc:threadSafeInit- )

    解决程序在xp系统总是莫名奇妙的崩溃问题(/Zc:threadSafeInit- )

    现象:程序在xp系统上面总是莫名其妙的崩溃,检查代码看不出任何问题,感觉代码都很好。即使你远程调试,找到了崩溃的点,当你注释了崩溃点之后,还是会崩溃到别的地方。当你遇到了这种情况的时候,不妨参照一下下面的方法看看,说不定可以解决问题。如何解决?将崩溃程序相关的所有工程代码全部关闭全局变量的线程安全检...

    uafxcwd.lib(afxmem.obj) : error LNK2005:

    uafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)"解决办法

    如果在编译MFC程序的时候出现下列及类似的错误: 1˃uafxcwd.lib(afxmem.obj) : error LNK2005: "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z) 已经在 LIBCMTD.lib(new...

    c++函数模板参数类型限定

    c++函数模板参数类型限定

    函数模板函数模板可以实现对不同数据类型做统一操作,比如比较两个数据的大小:template<typename T> bool compare(T& a,T& b) {   return a...

    排序算法-冒泡排序

    排序算法-冒泡排序

    冒泡排序也是一种简单直观的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由冒泡排序(英语:Bubble Sort)又称为泡式排序,是一种简单的排序算法。它重复地走访过...