Para exibir o menu, eu uso a TrackPopupMenuEx
função. O menu que eu quero exibir eu obtenho diretamente da extensão do shell. Este é o menu para o item "New"
( HKEY_CLASSES_ROOT\Directory\Background\shellex\ContextMenuHandlers\New
) que é exibido no menu de contexto de fundo da pasta.
Consegui obter o menu em si, mas não consigo exibi-lo corretamente. Ao tentar exibir o menu, os seguintes problemas aparecem:
- Se eu passar diretamente
hNewMenu
(hNewMenu
éHMENU
obtido viaQueryContextMenu
) paraTrackPopupMenuEx
, então o item em si chamado"New"
não é exibido, mas seus elementos filhos são mostrados diretamente - Os itens de menu que são exibidos não funcionam (Não podem ser executados via
InvokeCommand
). No entanto, eles funcionarão se você passar o menu paraTrackPopupMenuEx
asGetSubMenu(hNewMenu, 0)
, mas neste caso, é claro,"New"
não devem ser visíveis
Tentei criar um novo menu hRootMenu
com um item raiz "New"
onde os itens de GetSubMenu(hNewMenu, 0)
poderiam ser colocados como filhos. No entanto, não funcionou, o item pai adicionado manualmente "New"
ainda não é exibido e, em vez disso, os submenus de "New"
são exibidos diretamente.
Minha pergunta é: como posso exibir o item "New"
com seus filhos dentro e para que os itens do menu possam ser executados via IContextMenu::InvokeCommand
?
Você pode ver meu código abaixo. Para executar esse código e exibir o menu, você precisa fazer o seguinte:
- Substitua o
path
valor da variável pelo caminho para qualquer pasta - Pressione o
Context menu
botão para exibir o menu de contexto
#include <Windows.h>
#include <ShlObj.h>
#include <sstream>
HINSTANCE hInst;
IContextMenu2* cm2;
IContextMenu3* cm3;
const wchar_t* path = L"C:\\Users\\Username\\Desktop\\folder";
bool UpdateContextMenu(LPCONTEXTMENU icm1, void** ppContextMenu)
{
*ppContextMenu = NULL;
if (icm1)
{ // since we got an IContextMenu interface we can
// now obtain the higher version interfaces via that
if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu3, ppContextMenu))) {
cm3 = (LPCONTEXTMENU3)*ppContextMenu;
}
else if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu2, ppContextMenu))) {
cm2 = (LPCONTEXTMENU2)*ppContextMenu;
}
if (*ppContextMenu) {
icm1->Release(); // we can now release version 1 interface,
// cause we got a higher one
}
else {
*ppContextMenu = icm1; // since no higher versions were found
} // redirect ppContextMenu to version 1 interface
}
else
return false; // something went wrong
return true; // success
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (cm3) {
LRESULT res;
if (SUCCEEDED(cm3->HandleMenuMsg2(message, wParam, lParam, &res))) {
return res;
}
}
else if (cm2) {
if (SUCCEEDED(cm2->HandleMenuMsg(message, wParam, lParam))) {
return 0;
}
}
switch (message) {
case WM_CREATE: {
CreateWindowA(
"BUTTON", "Context menu",
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
100, 100, 100, 30,
hWnd, (HMENU)1, hInst, nullptr);
break;
}
case WM_COMMAND: {
if (LOWORD(wParam) == 1) {
LPITEMIDLIST pidl;
SFGAOF sfgao;
if (SUCCEEDED(SHParseDisplayName(path, NULL, &pidl, 0, &sfgao))) {
IShellFolder* psf;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild))) {
psf->Release();
}
}
IContextMenu* outPcm = nullptr;
CLSID clsid2;
CLSIDFromString(L"{D969A300-E7FF-11d0-A93B-00A0C90F2719}", &clsid2); // CLSID_NewMenu
const int SCRATCH_QCM_FIRST = 1;
const int SCRATCH_QCM_LAST = 0x7FFF;
IShellExtInit* shExtInit;
if (SUCCEEDED(CoCreateInstance(clsid2, NULL, CLSCTX_INPROC_SERVER, IID_IShellExtInit, (LPVOID*)&shExtInit))) {
if (SUCCEEDED(shExtInit->Initialize(pidl, NULL, NULL))) {
IContextMenu* cm = NULL;
if (SUCCEEDED(shExtInit->QueryInterface(IID_IContextMenu, (void**)&cm))) {
UpdateContextMenu(cm, (void**)&outPcm);
HMENU hNewMenu = CreatePopupMenu();
HMENU hRootMenu = CreatePopupMenu();
if (SUCCEEDED(outPcm->QueryContextMenu(hNewMenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) {
AppendMenu(hRootMenu, MF_STRING | MF_POPUP, (UINT_PTR)GetSubMenu(hNewMenu, 0), L"New");
int idCmd = TrackPopupMenuEx(hRootMenu, TPM_RETURNCMD, 100, 100, (HWND)hWnd, NULL);
CMINVOKECOMMANDINFO cmi = { 0 };
cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(idCmd - SCRATCH_QCM_FIRST);
cmi.nShow = SW_SHOWNORMAL;
HRESULT hr = outPcm->InvokeCommand(&cmi);
if (!SUCCEEDED(hr)) {
std::stringstream ss;
ss << "GetLastError() = " << GetLastError() << " hr = " << hr;
MessageBoxA(NULL, ss.str().c_str(), "", 0);
}
outPcm->Release();
cm2 = nullptr;
cm3 = nullptr;
}
}
}
}
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
hInst = hInstance;
WNDCLASSA wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = "MYWINDOWCLASS";
RegisterClassA(&wc);
HWND hWnd = CreateWindowExA(
0, "MYWINDOWCLASS", "Test", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, hInstance, nullptr
);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Por alguma razão, a implementação espera que a
WM_INITMENUPOPUP
mensagem passada paraHandleMenuMsg2
tenha o identificador de menu para o submenu que ele criou emQueryContextMenu
([New >] [New]
). Presumo que o menu fornecido porIShellFolder
verifica o ID do item de menu para saber para qual extensão de shell encaminhar a mensagem e o menu Novo nunca é o primeiro item de menu lá, então esse problema não acontece no Explorer.Esta solução alternativa também corrige
InvokeCommand
, mas a implementação espera que você forneçaIContextMenu
um site que implementeIShellView
para obter os recursos completos do assistente de renomeação e atalho.Não me sinto muito bem com esse hack, usar o
IContextMenu
formato realIShellFolder
é muito melhor se você quiser o menu de fundo completo.