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

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

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

右键菜单,即用户右击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++智能指针基本原理

    什么是智能指针?最简单来说就是会自动释放内存的指针,使用方便,不用担心内存泄漏问题。它其实就是通过封装,利用对象的析构函数释放申请的内存,基本上自动释放的用法都是利用析构函数去做一些释放工作。如:自动释放的句柄智能指针的基本实现class TestClass {  p...

    vs(vs2015)拖动或停靠窗口崩溃的解决方法

    vs(vs2015)拖动或停靠窗口崩溃的解决方法

    使用vs拖动窗口后想要停靠某一个区域时,ide会崩溃重启,而且会反复这样,拖动布局功能基本丧失,使用起来非常不爽。解决办法如下: vs2019(visual studio 2019) 如果vs版本是2019,那么直接升级vs即可解决问题。 vs2015(visual studio...

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

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

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

    C++ 获取进程所在目录(进程全路径)

    C++ 获取进程所在目录(进程全路径)

    打开windows任务管理器,会看到很多的进程在运行,随机挑选一个,如何通过c++代码获取某一个进程的所在全路径呢?这也是在windows软件开发中经常遇到的需求。通过进程名获取进程全路径由于可能很多进程叫同一个名字,所以获得的结果也有可能是多个#include <windows.h...

    c++为什么不能在构造函数里调用虚函数?

    c++为什么不能在构造函数里调用虚函数?

    c++为什么不能在构造函数里调用虚函数? c++的构造顺序先构造父类,然后构造子类,析构顺序相反。 如果在构造函数调用虚函数,例如:ClassB继承于ClassA,如果在ClassA的构造函数里调用虚函数,此时因为ClassB并没有构造,所以ClassB的成员都没有初始化,如果编译执行...