|
由于工作的需求要實現像winrara那樣的shell擴展菜單,帶圖標的,針對某個類型的文件執行相應的操作, 查了不少資料,終于解決了,效果如下圖中紅色矩形部分。

最先想到的當然是最方便的,直接添加注冊表,只需在HCLR中需要處理的類型(即右鍵選中的文件類型)的注冊表項中的shell子鍵中加入新項, 修改默認值為顯示名稱,顯示效果如上圖的<用“記事本”打開>菜單項。(*)代表任意類型的文件。

然后再新項中加入command子鍵,默認值設置為運行程序的參數,如"notepad.exe %1”,其中%1代表右鍵選中的文件。
這樣當點擊菜單后就可以執行你指定的程序("notepad")并傳入選中的文件名作為命令參數,這樣就可以處理這個文件了。
其實這樣足以滿足需求了,但效果上還差了一點,很明顯少了個圖標。既然winrar都能實現,肯定是還有其它實現的方法了。
于是就是處理圖標了,然而最終沒能找到好的方法,但如果是win7的話,還是有的,那就是在command中加入icon項,值為
你的目標程序,然而如果是xp就行不通了。
本想簡單的解決,但沒想到還是得用上復雜的方法,僅僅為了一個圖標 - -;。
復雜的方法只能是通過擴展shell接口來實現,首先肯定得牽涉到COM,所以關于COM可以上網查下資料,再這里就不多說(其實
讓我講我也不一定講得清楚),這里我就寫下得注意的地方就是了。
1.創建工程這里我用的VS2005,創建一個ATL 項目,屬性不需要改,點默認的就可以了。
2.右擊項目,添加,類,添加一個新ATL簡單對象。
3.編輯代碼,關于IShellExtInit和IContextMenu接口可以查看MSDN,上面寫的很詳細。
// CCContextMenuExt

class ATL_NO_VTABLE CCContextMenuExt :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CCContextMenuExt, &CLSID_CContextMenuExt>,
 public IDispatchImpl<ICContextMenuExt, &IID_ICContextMenuExt, &LIBID_CtxMenuExtLib, /**//*wMajor =*/ 1, /**//*wMinor =*/ 0>,
public IShellExtInit,
public IContextMenu
  {
public:
CCContextMenuExt()
 {
}

DECLARE_REGISTRY_RESOURCEID(IDR_CCONTEXTMENUEXT)


BEGIN_COM_MAP(CCContextMenuExt)
COM_INTERFACE_ENTRY(ICContextMenuExt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IShellExtInit)
COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()



DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
 {
m_hBitmap = LoadBitmap(_hInstance, MAKEINTRESOURCE(IDB_MENU));
return S_OK;
}

void FinalRelease()
 {
if (m_hBitmap)
 {
DeleteObject(m_hBitmap);
}
}

public:
enum
 {
IDM_CTXMENU = 0,
};

public:

HRESULT STDMETHODCALLTYPE Initialize(
 /**//* [in] */ LPCITEMIDLIST pidlFolder,
 /**//* [in] */ IDataObject *pdtobj,
 /**//* [in] */ HKEY hkeyProgID)
 {
HRESULT hr;
UINT nFileCount;
UINT nLen;

FORMATETC fmt =
 {
CF_HDROP,
NULL,
DVASPECT_CONTENT,
-1,
TYMED_HGLOBAL
};

STGMEDIUM sm =
 {
TYMED_HGLOBAL
};

hr = pdtobj->GetData(&fmt, &sm);
if (FAILED(hr))
 {
return hr;
}

nFileCount = DragQueryFile((HDROP)sm.hGlobal, 0xFFFFFFFF, NULL, 0);

if (nFileCount >= 1)
 {
nLen = DragQueryFile((HDROP)sm.hGlobal, 0, m_pszFileName, sizeof(m_pszFileName));
if (nLen >0 && nLen <MAX_PATH)
 {
m_pszFileName[nLen] = _T('\0');
hr = S_OK;
}
else
 {
hr = E_INVALIDARG;
}
}
else
 {
hr = E_INVALIDARG;
}

ReleaseStgMedium(&sm);

return hr;
}

STDMETHOD(QueryContextMenu)(THIS_
HMENU hmenu,
UINT indexMenu,
UINT idCmdFirst,
UINT idCmdLast,
UINT uFlags)
 {
MENUITEMINFO mii;

if (uFlags & CMF_DEFAULTONLY)
 {
return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, 0);
}

memset((void*)&mii, 0, sizeof(mii));
mii.cbSize = sizeof(mii);
mii.fMask = MIIM_STRING | MIIM_CHECKMARKS | MIIM_ID | MIIM_STATE;
mii.cch = lstrlen(SZ_MENUTEXT);
mii.dwTypeData = SZ_MENUTEXT;

 /**//*
這里用hbmpChecked而不用hbmpItem的原因
- -自己動手試試就知道了。
*/
mii.hbmpItem
mii.hbmpChecked = m_hBitmap;
mii.hbmpUnchecked = m_hBitmap;
mii.fState = MFS_ENABLED;
mii.wID = idCmdFirst + indexMenu;

if (!InsertMenuItem(hmenu, indexMenu, TRUE, &mii))
 {
return E_FAIL;
}

lstrcpynA(m_pszVerb, "protected_run", 32);
lstrcpynW(m_pwszVerb, L"protected_run", 32);

return MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_NULL, IDM_CTXMENU + 1);
}

STDMETHOD(InvokeCommand)(THIS_
LPCMINVOKECOMMANDINFO lpici)
 {
BOOL fEx = FALSE;
BOOL fUnicode = FALSE;

if(lpici->cbSize == sizeof(CMINVOKECOMMANDINFOEX))
 {
fEx = TRUE;
if((lpici->fMask & CMIC_MASK_UNICODE))
 {
fUnicode = TRUE;
}
}

if( !fUnicode && HIWORD(lpici->lpVerb))
 {
if(StrCmpIA(lpici->lpVerb, m_pszVerb))
 {
return E_FAIL;
}
}

else if( fUnicode && HIWORD(((CMINVOKECOMMANDINFOEX *) lpici)->lpVerbW))
 {
if(StrCmpIW(((CMINVOKECOMMANDINFOEX *)lpici)->lpVerbW, m_pwszVerb))
 {
return E_FAIL;
}
}

else if(LOWORD(lpici->lpVerb) != IDM_CTXMENU)
 {
return E_FAIL;
}

else
 {
//在此處理點擊事件.
MessageBox(NULL, m_pszFileName, _T(""), MB_OK);

return S_OK;
}

return E_FAIL;

}

STDMETHOD(GetCommandString)(THIS_
UINT_PTR idCmd,
UINT uType,
UINT * pwReserved,
LPSTR pszName,
UINT cchMax)
 {
HRESULT hr = E_INVALIDARG;
static CHAR szHelpTextA[] = "windows擴展菜單!";
static WCHAR szHelpTextW[] = L"windows擴展菜單!";

if(idCmd != IDM_CTXMENU)
 {
return hr;
}

switch(uType)
 {
case GCS_HELPTEXTA:
lstrcpynA((CHAR*)pszName, szHelpTextA, cchMax);
break;

case GCS_HELPTEXTW:
lstrcpynW((WCHAR*)pszName, szHelpTextW, cchMax);;
break;

case GCS_VERBA:
lstrcpynA((CHAR*)pszName, m_pszVerb, cchMax);
break;

case GCS_VERBW:
lstrcpynW((WCHAR*)pszName, m_pwszVerb, cchMax);
break;

default:
hr = S_OK;
break;
}
return hr;
}

private:
TCHAR m_pszFileName[MAX_PATH];
HBITMAP m_hBitmap;
CHAR m_pszVerb[32];
WCHAR m_pwszVerb[32];

};
4.修改服務注冊、取消注冊函數,這里只需在需要處理的文件類型的shllex下的ContextMenuHandlers下創建項,并設置接口ID。
// DllRegisterServer - 將項添加到系統注冊表
STDAPI DllRegisterServer(void)
  {
// 注冊對象、類型庫和類型庫中的所有接口
HRESULT hr;
HKEY hKey;

static char pszGUID[] = "{C2397F2E-4BA3-4B9D-858A-F775761C023B}";

hr = _AtlModule.DllRegisterServer();
if (FAILED(hr))
 {
return hr;
}

if (RegCreateKeyA(HKEY_CLASSES_ROOT,
"*\\shellex\\ContextMenuHandlers\\CtxMenu", &hKey) != ERROR_SUCCESS)
 {
return E_FAIL;
}

if (RegSetValueA(hKey, NULL, REG_SZ, pszGUID,
(DWORD)strlen(pszGUID)) != ERROR_SUCCESS)
 {
RegCloseKey(hKey);
return E_FAIL;
}

return hr;
}


// DllUnregisterServer - 將項從系統注冊表中移除
STDAPI DllUnregisterServer(void)
  {
RegDeleteKeyA(HKEY_CLASSES_ROOT, "*\\shellex\\ContextMenuHandlers\\CtxMenu");

return _AtlModule.DllUnregisterServer();
}
5.編譯運行,VS會自動替你注冊,當然也可以用regsvr32 自己注冊。
下載地址:http://www.shnenglu.com/Files/shly/CtxMenuExt.rar
注意:注冊后的DLL無法刪除,有種方法可以就是regsvr32 /u 取消注冊dll, 然后重啟explorer.exe。
|