静默调用ShellContextMenu 实现QQ文件共享
我在CSDN提问题一直没人回复,一下午时间自己终于解决了问题
http://bbs.csdn.net/topics/391916381
现将过程录下
先说需求,我想实现的功能是 在程序中对文件调用百度网盘/qq的接口,发送给好友或上传到网盘,实现思路是右键菜单
现在我已经实现了在我的窗口中能够调出系统的右键菜单,并实现接口。
思路是 IShellFolder->ParseDisplayName 得到 文件到PIDL,IShellFolder->GetUIObjectOf() 得到IContextMenu 接口。
然后IContextMenu->QueryContextMenu() 得到菜单,再弹出菜单后,根据返回值调用 IContextMenu->InvokeCommand() 实现对文件的命令。
现在问题来了。
1. 在资源管理器中右键是有百度云盘的,但是在自己的窗口中没有。
2. 如何不弹出菜单,直接调用命令
3. 有可能右键菜单没有,但是QQShellExt YunShellExt 的COM接口还是存在的,有没有可能跳过ShellFolder->GetUiObjectOf 直接得到与云盘或QQ相关的 IContextMenu 或单独初始化一个。也就是有没有可能直接调用 QQShellExt 或 YunShellExt 的接口?
下面是我测试右键菜单的相关代码:
ShellContextMenu.h
1 #pragma once 2 class CShellContextMenu 3 { 4 public: 5 CShellContextMenu(); 6 ~CShellContextMenu(); 7 8 public: 9 10 11 bool ShowContextMenu(const CStringArray& files, HWND hWnd, LPPOINT pt); 12 13 14 IShellFolder* GetDesktopFolder(); 15 IShellFolder* GetParentFolder(LPCTSTR szFolder); 16 17 CString GetDirectory(LPCTSTR szFile); 18 CString GetFileNameWithExt(LPCTSTR szFile); 19 20 bool GetPidls(const CStringArray& files); 21 22 private: 23 IContextMenu* GetContextMenuInterfaces(IShellFolder* pShellFolder, CArray<LPCITEMIDLIST>& idls); 24 bool InvokeCmd(IContextMenu* pContext, LPCTSTR szCmd, LPCTSTR szFolder); 25 bool InvokeCmd(IContextMenu* pContext, int nCmdSelection, LPCTSTR szFolder, LPPOINT pt); 26 bool ShowContextMenu(HWND hWnd, LPPOINT pt); 27 28 void ReleaseIdls(); 29 void ReleaseAll(); 30 31 private: 32 CArray<LPCITEMIDLIST> m_idls; 33 34 IShellFolder* m_pDesktopFolder; 35 IShellFolder* m_pParentFolder; 36 IContextMenu* m_pContextMenu; 37 IContextMenu2* m_pContextMenu2; 38 IContextMenu3* m_pContextMenu3; 39 40 CString m_strParentFolder; 41 };
ShellContextMenu.cpp
1 #include "stdafx.h" 2 #include "ShellContextMenu.h" 3 4 5 CShellContextMenu::CShellContextMenu() 6 { 7 m_pDesktopFolder = nullptr; 8 m_pParentFolder = nullptr; 9 10 m_pContextMenu = nullptr; 11 m_pContextMenu2 = nullptr; 12 m_pContextMenu3 = nullptr; 13 } 14 15 16 CShellContextMenu::~CShellContextMenu() 17 { 18 ReleaseAll(); 19 } 20 21 bool CShellContextMenu::ShowContextMenu(const CStringArray& files, HWND hWnd, LPPOINT pt) 22 { 23 ReleaseAll(); 24 25 if (!GetPidls(files)) 26 { 27 return false; 28 } 29 30 return ShowContextMenu(hWnd, pt); 31 } 32 33 IShellFolder* CShellContextMenu::GetDesktopFolder() 34 { 35 // 获取桌面指针 36 if (nullptr == m_pDesktopFolder) 37 { 38 if (S_OK != SHGetDesktopFolder(&m_pDesktopFolder)) 39 { 40 return nullptr; 41 } 42 } 43 44 return m_pDesktopFolder; 45 } 46 47 IShellFolder* CShellContextMenu::GetParentFolder(LPCTSTR szFolder) 48 { 49 if (nullptr == m_pParentFolder) 50 { 51 auto pDesktop = GetDesktopFolder(); 52 if (nullptr == pDesktop) 53 { 54 return nullptr; 55 } 56 57 ULONG pchEaten = 0; 58 LPITEMIDLIST pidl = nullptr; 59 DWORD dwAttributes = 0; 60 auto hr = pDesktop->ParseDisplayName(nullptr, nullptr, (LPTSTR)szFolder, nullptr, &pidl, nullptr); 61 if (S_OK != hr) 62 { 63 return nullptr; 64 } 65 66 STRRET sRetName; 67 hr = pDesktop->GetDisplayNameOf(pidl, SHGDN_FORPARSING, &sRetName); 68 if (S_OK != hr) 69 { 70 CoTaskMemFree(pidl); 71 return nullptr; 72 } 73 74 hr = StrRetToBuf(&sRetName, pidl, m_strParentFolder.GetBuffer(_MAX_PATH), _MAX_PATH); 75 m_strParentFolder.ReleaseBuffer(); 76 if (S_OK != hr) 77 { 78 CoTaskMemFree(pidl); 79 return nullptr; 80 } 81 82 IShellFolder* pParentFolder = nullptr; 83 hr = pDesktop->BindToObject(pidl, nullptr, IID_IShellFolder, (void**)&pParentFolder); 84 if (S_OK != hr) 85 { 86 CoTaskMemFree(pidl); 87 return nullptr; 88 } 89 90 CoTaskMemFree(pidl); 91 92 m_pParentFolder = pParentFolder; 93 94 } 95 96 return m_pParentFolder; 97 } 98 99 CString CShellContextMenu::GetDirectory(LPCTSTR szFile) 100 { 101 TCHAR szDrive[_MAX_DRIVE]; 102 TCHAR szDir[_MAX_DIR]; 103 TCHAR szFName[_MAX_FNAME]; 104 TCHAR szExt[_MAX_EXT]; 105 _tsplitpath_s(szFile, szDrive, szDir, szFName, szExt); 106 107 CString strResult = szDrive; 108 strResult += szDir; 109 return strResult; 110 } 111 112 CString CShellContextMenu::GetFileNameWithExt(LPCTSTR szFile) 113 { 114 TCHAR szDrive[_MAX_DRIVE]; 115 TCHAR szDir[_MAX_DIR]; 116 TCHAR szFName[_MAX_FNAME]; 117 TCHAR szExt[_MAX_EXT]; 118 _tsplitpath_s(szFile, szDrive, szDir, szFName, szExt); 119 120 CString strResult = szFName; 121 strResult += szExt; 122 return strResult; 123 } 124 125 bool CShellContextMenu::GetPidls(const CStringArray& files) 126 { 127 ReleaseIdls(); 128 129 if (files.IsEmpty()) 130 { 131 return false; 132 } 133 134 auto pParentFolder = GetParentFolder(GetDirectory(files[0])); 135 if (nullptr == pParentFolder) 136 { 137 return false; 138 } 139 140 for (int i = 0; i < files.GetSize(); i++) 141 { 142 CString strFile = GetFileNameWithExt(files[i]); 143 144 ULONG pchEaten = 0; 145 LPITEMIDLIST pidl = nullptr; 146 DWORD dwAttributes = 0; 147 auto hr = pParentFolder->ParseDisplayName(nullptr, nullptr, (LPTSTR)(LPCTSTR)strFile, &pchEaten, &pidl, &dwAttributes); 148 if (S_OK != hr) 149 { 150 continue; 151 } 152 153 m_idls.Add(pidl); 154 155 } 156 157 return !m_idls.IsEmpty(); 158 159 } 160 161 IContextMenu* CShellContextMenu::GetContextMenuInterfaces(IShellFolder* pShellFolder, CArray<LPCITEMIDLIST>& idls) 162 { 163 IContextMenu* pResult = nullptr; 164 UINT refReversed = 0; 165 auto hr = pShellFolder->GetUIObjectOf(nullptr, idls.GetSize(), idls.GetData(), IID_IContextMenu, &refReversed, (void**)&pResult); 166 if (S_OK != hr) 167 { 168 return nullptr; 169 } 170 171 return pResult; 172 } 173 174 bool CShellContextMenu::InvokeCmd(IContextMenu* pContext, LPCTSTR szCmd, LPCTSTR szFolder) 175 { 176 CMINVOKECOMMANDINFOEX info; 177 info.cbSize = sizeof(CMINVOKECOMMANDINFOEX); 178 info.lpVerbW = szCmd; 179 info.lpDirectoryW = szFolder; 180 info.fMask = CMIC_MASK_UNICODE; 181 182 return S_OK == pContext->InvokeCommand((CMINVOKECOMMANDINFO*)&info); 183 184 } 185 186 /// <summary> 187 /// 调用命令 188 /// </summary> 189 /// <param name="pContext"></param> 190 /// <param name="nCmdSelection"></param> 191 /// <param name="szFolder"></param> 192 /// <returns></returns> 193 bool CShellContextMenu::InvokeCmd(IContextMenu* pContext, int nCmdSelection, LPCTSTR szFolder, LPPOINT pt) 194 { 195 try 196 { 197 USES_CONVERSION; 198 CMINVOKECOMMANDINFOEX invoke; 199 memset(&invoke, 0, sizeof(CMINVOKECOMMANDINFOEX)); 200 invoke.cbSize = sizeof(CMINVOKECOMMANDINFOEX); 201 invoke.lpVerb = (LPCSTR)(nCmdSelection - CDM_FIRST); 202 invoke.lpDirectory = CT2CA(szFolder); 203 invoke.lpVerbW = (LPCTSTR)(nCmdSelection - CDM_FIRST); 204 invoke.lpDirectoryW = szFolder; 205 invoke.fMask = CMIC_MASK_UNICODE; 206 // invoke.ptInvoke.x = pt->x; 207 // invoke.ptInvoke.y = pt->y; 208 invoke.nShow = SW_SHOWNORMAL; 209 210 auto hr = pContext->InvokeCommand((CMINVOKECOMMANDINFO*)&invoke); 211 return S_OK == hr; 212 213 } 214 catch (...) 215 { 216 return false; 217 } 218 } 219 220 bool CShellContextMenu::ShowContextMenu(HWND hWnd, LPPOINT pt) 221 { 222 if (m_idls.IsEmpty()) 223 { 224 ReleaseAll(); 225 return false; 226 } 227 228 m_pContextMenu = GetContextMenuInterfaces(m_pParentFolder, m_idls); 229 if (nullptr == m_pContextMenu) 230 { 231 ReleaseAll(); 232 return false; 233 } 234 235 auto hMenu = ::CreatePopupMenu(); 236 auto hr = m_pContextMenu->QueryContextMenu(hMenu, 0, CDM_FIRST, CDM_LAST, CMF_EXPLORE | CMF_NORMAL); 237 if (HRESULT_SEVERITY(hr) != SEVERITY_SUCCESS) 238 { 239 ReleaseAll(); 240 return false; 241 } 242 243 m_pContextMenu->QueryInterface(IID_IContextMenu2, (void**)&m_pContextMenu2); 244 m_pContextMenu->QueryInterface(IID_IContextMenu3, (void**)&m_pContextMenu3); 245 246 auto nSel = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, pt->x, pt->y, hWnd, nullptr); 247 if (0 == nSel) 248 { 249 auto error = ::GetLastError(); 250 251 CString str; 252 ::FormatMessageW( 253 FORMAT_MESSAGE_ALLOCATE_BUFFER | 254 FORMAT_MESSAGE_FROM_SYSTEM | 255 FORMAT_MESSAGE_IGNORE_INSERTS, 256 NULL, 257 error, 258 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 259 str.GetBuffer(1024), 260 1024, NULL); 261 262 str.ReleaseBuffer(); 263 } 264 DestroyMenu(hMenu); 265 266 // CString strCmd; 267 // hr = m_pContextMenu->GetCommandString(nSel, GCS_VALIDATE | GCS_VERB | GCS_UNICODE, nullptr, (char*)strCmd.GetBuffer(1024), 1024); 268 // strCmd.ReleaseBuffer(); 269 // 270 // CString strHelpr; 271 // hr = m_pContextMenu->GetCommandString(nSel,GCS_VALIDATE | GCS_HELPTEXT | GCS_UNICODE, nullptr, (char*)strHelpr.GetBuffer(1024), 1024); 272 // strHelpr.ReleaseBuffer(); 273 // if (S_OK == hr) 274 // { 275 // InvokeCmd(m_pContextMenu, strCmd, m_strParentFolder); 276 // } 277 278 if (nSel > 0) 279 { 280 InvokeCmd(m_pContextMenu, nSel, m_strParentFolder, pt); 281 } 282 283 ReleaseAll(); 284 return true; 285 } 286 287 void CShellContextMenu::ReleaseIdls() 288 { 289 for (int i = 0; i < m_idls.GetSize(); i++) 290 { 291 if (nullptr != m_idls[i]) 292 { 293 CoTaskMemFree((void*)m_idls[i]); 294 } 295 } 296 297 m_idls.RemoveAll(); 298 } 299 300 void CShellContextMenu::ReleaseAll() 301 { 302 ReleaseIdls(); 303 304 #define _ReleaseComPtr(p) \ 305 if (nullptr != (p)){(p)->Release(); (p) = nullptr;} 306 307 _ReleaseComPtr(m_pContextMenu3); 308 _ReleaseComPtr(m_pContextMenu2); 309 _ReleaseComPtr(m_pContextMenu); 310 _ReleaseComPtr(m_pParentFolder); 311 _ReleaseComPtr(m_pDesktopFolder); 312 313 #undef _ReleaseComPtr 314 }
call
void CTestContextMenuCppDlg::OnRButtonUp(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CShellContextMenu cxMenu; CStringArray ar; ar.Add(_T("D:\\a.txt")); ClientToScreen(&point); cxMenu.ShowContextMenu(ar, GetSafeHwnd(), &point); CDialogEx::OnRButtonUp(nFlags, point); }
想要实现的形式:
CStringArray ar; ar.Add(_T("D:\\a.txt")); QQExt ext; ext.发给好友(ar);// 调用右键菜单 发给好友
然后,我开始一边研究一边等待CSDN上能有人帮我解决这个问题。
更新1:
以上代码其实完全从 http://www.jackspace.cn/html/0528745226.html 抄来的,说是国外人写的,原文没找到。
更新2:
又找到这篇文章
http://bcbjournal.org/articles/vol4/0006/Using_the_shell_context_menu.htm
说系统菜单中的以下动作可以直接调用
Verb
openas
cut
copy
paste
link
delete
properties
Explore
find
COMPRESS
UNCOMPRESS
更新3:
#define CLSID_QQ_EXT "{53D2405C-48AB-4C8A-8F59-CE0610F13BBC}" ::CoInitialize(nullptr); CLSID clsid; CLSIDFromString(_T(CLSID_QQ_EXT), &clsid); IContextMenu* pContextMenu = nullptr; auto hr = CoCreateInstance(clsid, nullptr, CLSCTX_ALL, IID_IContextMenu, (void**)&pContextMenu); if (S_OK != hr) { return false; } HMENU hMenu = CreatePopupMenu(); hr = pContextMenu->QueryContextMenu(hMenu, 0, CDM_FIRST, CDM_LAST, CMF_EXPLORE); if (HRESULT_SEVERITY(hr) != SEVERITY_SUCCESS) { return false; } auto nSel = ::TrackPopupMenuEx(hMenu, TPM_RETURNCMD, pt->x, pt->y, hWnd, nullptr);
这段代码可以跑得通,并且弹出一个空菜单,所以问题就是怎么给 QQExt一个文件,或者pidl
更新4:
m_pContextMenu = GetContextMenuInterfaces(m_pParentFolder, m_idls); if (nullptr == m_pContextMenu) { ReleaseAll(); return false; } auto pContextMenu = m_pContextMenu; auto hr = m_pContextMenu->QueryInterface(IID_IContextMenu2, (void**)&m_pContextMenu2); if (S_OK == hr) { pContextMenu = m_pContextMenu2; } hr = pContextMenu->QueryInterface(IID_IContextMenu3, (void**)&m_pContextMenu3); if (S_OK == hr) { pContextMenu = m_pContextMenu3; } auto hMenu = ::CreatePopupMenu(); hr = pContextMenu->QueryContextMenu(hMenu, 0, CDM_FIRST, CDM_LAST, CMF_EXPLORE | CMF_NORMAL | CMF_ASYNCVERBSTATE); if (HRESULT_SEVERITY(hr) != SEVERITY_SUCCESS) { ReleaseAll(); return false; } auto nSel = TrackPopupMenuEx(hMenu, TPM_RETURNCMD, pt->x, pt->y, hWnd, nullptr); if (0 == nSel) { auto error = ::GetLastError(); CString str; ::FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), str.GetBuffer(1024), 1024, NULL); str.ReleaseBuffer(); } DestroyMenu(hMenu); CString strCmd; hr = m_pContextMenu->GetCommandString(nSel - CDM_FIRST, GCS_VALIDATE | GCS_VERB | GCS_UNICODE, nullptr, (char*)strCmd.GetBuffer(1024), 1024); strCmd.ReleaseBuffer(); CString strHelpr; hr = m_pContextMenu->GetCommandString(nSel - CDM_FIRST,GCS_VALIDATE | GCS_HELPTEXT | GCS_UNICODE, nullptr, (char*)strHelpr.GetBuffer(1024), 1024); strHelpr.ReleaseBuffer(); // if (S_OK == hr) // { // InvokeCmd(m_pContextMenu, strCmd, m_strParentFolder); // } // if (nSel > 0) { InvokeCmd(pContextMenu, nSel, m_strParentFolder, pt); }
此至,这条路完全跑通,明天开始整理相关代码,并测试这段代码对百度网盘的适应情况。
在CSDN盯了一下午,没有任何回复,沮丧。最后还是自己解决 。