Windows右键菜单扩展
首先,需要建立ATL项目,按照如下项目创建工程。
创建好后把带PS删掉。
如何添加一个ATL简单对象,填类名,点添加。之后直接点完成。
点开ATLTestMenu.h,CATLTestMenu类须继承IContextMenu接口和IShellExtInit接口,并实现其虚方法。
需要加:两个头文件,继承的两个接口,COM_INTERFACE_ENTRY两个接口,4个需要实现的虚方法。
ATLTestMenu.h代码如下:
// ATLTestMenu.h: CATLTestMenu 的声明
#pragma once
#include "resource.h" // 主符号
#include "shlobj.h"
#include "comdef.h"
#include "ATLDemo_i.h"
#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Windows CE 平台(如不提供完全 DCOM 支持的 Windows Mobile 平台)上无法正确支持单线程 COM 对象。定义 _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA 可强制 ATL 支持创建单线程 COM 对象实现并允许使用其单线程 COM 对象实现。rgs 文件中的线程模型已被设置为“Free”,原因是该模型是非 DCOM Windows CE 平台支持的唯一线程模型。"
#endif
using namespace ATL;
// CATLTestMenu
class ATL_NO_VTABLE CATLTestMenu :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CATLTestMenu, &CLSID_ATLTestMenu>,
public IDispatchImpl<IATLTestMenu, &IID_IATLTestMenu, &LIBID_ATLDemoLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
public IContextMenu,
public IShellExtInit
{
public:
CATLTestMenu()
{
}
DECLARE_REGISTRY_RESOURCEID(106)
BEGIN_COM_MAP(CATLTestMenu)
COM_INTERFACE_ENTRY(IATLTestMenu)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IContextMenu)
COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
public:
HRESULT STDMETHODCALLTYPE Initialize(
/* [annotation][unique][in] */
_In_opt_ PCIDLIST_ABSOLUTE pidlFolder,
/* [annotation][unique][in] */
_In_opt_ IDataObject* pdtobj,
/* [annotation][unique][in] */
_In_opt_ HKEY hkeyProgID);
HRESULT STDMETHODCALLTYPE QueryContextMenu(
/* [annotation][in] */
_In_ HMENU hmenu,
/* [annotation][in] */
_In_ UINT indexMenu,
/* [annotation][in] */
_In_ UINT idCmdFirst,
/* [annotation][in] */
_In_ UINT idCmdLast,
/* [annotation][in] */
_In_ UINT uFlags);
HRESULT STDMETHODCALLTYPE InvokeCommand(
/* [annotation][in] */
_In_ CMINVOKECOMMANDINFO* pici);
HRESULT STDMETHODCALLTYPE GetCommandString(
/* [annotation][in] */
_In_ UINT_PTR idCmd,
/* [annotation][in] */
_In_ UINT uType,
/* [annotation][in] */
_Reserved_ UINT* pReserved,
/* [annotation][out] */
_Out_writes_bytes_((uType& GCS_UNICODE) ? (cchMax * sizeof(wchar_t)) : cchMax) _When_(!(uType& (GCS_VALIDATEA | GCS_VALIDATEW)), _Null_terminated_) CHAR* pszName,
/* [annotation][in] */
_In_ UINT cchMax);
WCHAR m_szFile[MAX_PATH] = { 0 };
};
OBJECT_ENTRY_AUTO(__uuidof(ATLTestMenu), CATLTestMenu)
初始化是右键时调用,可以判断是在空白处右键还是选中文件右键,获取选中的文件等操作。一旦返回E_INVALIDARG,后面的方法都不再继续调用了。
初始化通过后就开始创建菜单。
InvokeCommand是点击菜单项的相应函数。
ATLTestMenu.cpp代码如下:
// ATLTestMenu.cpp: CATLTestMenu 的实现
#include "pch.h"
#include "ATLTestMenu.h"
// CATLTestMenu
HRESULT __stdcall CATLTestMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID)
{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP hDrop;
//空白处右键,pdtobj是空指针
if (pdtobj == nullptr)
{
return E_INVALIDARG;
}
// 在数据对象内查找CF_HDROP类型数据。
// 如果没有数据,返回一个错误(“无效参数”)给Explorer。
if (FAILED(pdtobj->GetData(&fmt, &stg)))
{
return E_INVALIDARG;
}
// 取得指向实际数据的指针。
hDrop = (HDROP)GlobalLock(stg.hGlobal);
// 确保非NULL
if (NULL == hDrop)
{
return E_INVALIDARG;
}
//选中文件个数
UINT uNumFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
HRESULT hr = S_OK;
if (0 == uNumFiles)
{
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
return E_INVALIDARG;
}
// 取得第一个文件名,保存到 m_szFile
if (0 == DragQueryFile(hDrop, 0, m_szFile, MAX_PATH))
hr = E_INVALIDARG;
GlobalUnlock(stg.hGlobal);
ReleaseStgMedium(&stg);
return hr;
}
HRESULT __stdcall CATLTestMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags)
{
// 如果标识包含了 CMF_DEFAULTONLY,那么,什么都不做
if (uFlags & CMF_DEFAULTONLY)
{
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
}
InsertMenu(hmenu, indexMenu, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);//分割线
InsertMenu(hmenu, indexMenu + 1, MF_STRING | MF_BYPOSITION, idCmdFirst + 88, _T("Willow"));
InsertMenu(hmenu, indexMenu + 2, MF_STRING | MF_BYPOSITION, idCmdFirst + 99, _T("圆仔"));
InsertMenu(hmenu, indexMenu + 3, MF_SEPARATOR | MF_BYPOSITION, 0, NULL);//分割线
return MAKE_HRESULT(SEVERITY_SUCCESS, 0, USHORT(99 + 1));
}
HRESULT __stdcall CATLTestMenu::InvokeCommand(CMINVOKECOMMANDINFO* pici)
{
if (0 != HIWORD(pici->lpVerb))
{
return E_INVALIDARG;
}
// 取得命令索引,上面的+多少,就是多少,默认是0
switch (LOWORD(pici->lpVerb))
{
case 88:
{
TCHAR szMsg[MAX_PATH + 32];
wsprintf(szMsg, _T("被选中的文件:\n\n%s"), m_szFile);
MessageBox(pici->hwnd, szMsg, _T("弹窗1"),
MB_ICONINFORMATION);
return S_OK;
}
break;
case 99:
{
TCHAR szMsg[MAX_PATH + 32];
wsprintf(szMsg, _T("被选中的文件:\n\n%s"), m_szFile);
MessageBox(pici->hwnd, szMsg, _T("弹窗2"),
MB_ICONINFORMATION);
return S_OK;
}
break;
default:
return E_INVALIDARG;
break;
}
}
HRESULT __stdcall CATLTestMenu::GetCommandString(UINT_PTR idCmd, UINT uType, UINT* pReserved, CHAR* pszName, UINT cchMax)
{
return S_OK;
}
rgs文件是修改注册表的文件。approve键是加权限,HKCR下增加键是可以控制选中文件夹、指定类型文档、空白处等调用此dll。*是代表全部。
代码如下:
HKCR
{
NoRemove CLSID
{
ForceRemove {25fdbd79-4c1e-4669-afc9-0b1da1253ffc} = s 'ATLTestMenu class'
{
ForceRemove Programmable
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
TypeLib = s '{20aed5aa-6e0e-4f03-b95a-4a34191e341f}'
Version = s '1.0'
}
}
NoRemove *
{
NoRemove ShellEx
{
NoRemove ContextMenuHandlers
{
ForceRemove 'ATLTestMenu' = s '{25fdbd79-4c1e-4669-afc9-0b1da1253ffc}'
}
}
}
}
HKLM
{
NoRemove Software
{
NoRemove Microsoft
{
NoRemove Windows
{
NoRemove CurrentVersion
{
NoRemove Shell Extensions
{
NoRemove Approved
{
ForceRemove 'ATLTestMenu' = s '{25fdbd79-4c1e-4669-afc9-0b1da1253ffc}'
}
}
}
}
}
}
}
最后,将编译好的dll注册到系统中,电脑是64位的要编译x64的库,32位的编译x86的库。使用管理员权限运行cmd,输入如下命令行注册和反注册dll。
Regsvr32+空格+dll全路径
Regsvr32+空格 + /u+空格+dll全路径
操作成功或失败均有提示框。
下图是最终效果:
如果想获取右键空白处的路径,可以使用下面代码:
TCHAR szFileDir[MAX_PATH] = { 0 };
//pidlFolder是Initialize方法中第一个参数
SHGetPathFromIDList(pidlFolder, szFileDir);
有问题欢迎大家一起交流。