Reputation: 359
I am trying to display a right-click background context menu for a folder.
The menu is displayed, but there are the following problems:
If I specify the path to the folder as C:\Users\Username\Desktop\Folder\Child, I get a menu not for this folder, but for its parent, for C:\Users\Username\Desktop\Folder (this can be seen if you execute the "Properties" item, see: "Location" in properties)
Despite calling HandleMenuMsg and HandleMenuMsg2, the submenus for "Give access to" are not shown
Also, menu items installed by third-party programs do not work (for example, "Git Gui Here", "Git Bash Here", ...)
To get a right-click background menu for a folder, I use IShellFolder2::CreateViewObject
You can see my code below. To run this code and display the menu, you need to do the following:
#include <windows.h>
#include <shlobj.h>
#include <string>
#include <sstream>
#include <iomanip>
HINSTANCE hInst;
LPCSTR szTitle = "WinAPI";
LPCSTR szWindowClass = "MYWINDOWCLASS";
const wchar_t* filePath = L"C:\\Users\\Username\\Desktop\\Folder\\Child";
class ContextMenu
{
public:
IContextMenu2* getIContextMenu2()
{
return m_contextMenu2;
}
IContextMenu3* getIContextMenu3()
{
return m_contextMenu3;
}
void showContextMenu(const std::wstring& path, HWND hwnd, UINT xPos, UINT yPos)
{
IContextMenu* pcm = nullptr;
IContextMenu* outPcm = nullptr;
if (SUCCEEDED(GetUIObjectOfFolder(hwnd, path.c_str(), IID_IContextMenu, (void**)&pcm))) {
if (GetContextMenu(pcm, (void**)&outPcm)) {
HMENU hmenu = CreatePopupMenu();
if (hmenu) {
if (SUCCEEDED(outPcm->QueryContextMenu(hmenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_CANRENAME))) {
int idCmd = TrackPopupMenuEx(hmenu, TPM_RETURNCMD, xPos, yPos, (HWND)hwnd, NULL);
InvokeCommand(outPcm, idCmd);
if (m_contextMenu2) {
m_contextMenu2 = nullptr;
}
if (m_contextMenu3) {
m_contextMenu3 = nullptr;
}
outPcm->Release();
}
}
}
}
}
private:
const int SCRATCH_QCM_FIRST = 1;
const int SCRATCH_QCM_LAST = 0x7FFF;
IContextMenu2* m_contextMenu2 = nullptr;
IContextMenu3* m_contextMenu3 = nullptr;
HRESULT GetUIObjectOfFolder(HWND hwnd, LPCWSTR pszPath, REFIID riid, void** ppv)
{
*ppv = NULL;
HRESULT hr;
LPITEMIDLIST pidl;
SFGAOF sfgao;
if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, &sfgao))) {
IShellFolder2* psf;
LPCITEMIDLIST pidlChild;
if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder2, (void**)&psf, &pidlChild))) {
hr = psf->CreateViewObject(hwnd, riid, ppv);
psf->Release();
}
CoTaskMemFree(pidl);
}
return hr;
}
void InvokeCommand(IContextMenu* pContextMenu, UINT idCommand)
{
CMINVOKECOMMANDINFO cmi = { 0 };
cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
cmi.lpVerb = (LPSTR)MAKEINTRESOURCE(idCommand - 1);
cmi.nShow = SW_SHOWNORMAL;
if (!SUCCEEDED(pContextMenu->InvokeCommand(&cmi))) {
std::stringstream ss;
ss << "Last error: " << GetLastError();
std::string report = ss.str();
MessageBoxA(NULL, report.c_str(), report.c_str(), 0);
}
}
BOOL GetContextMenu(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))) {
m_contextMenu3 = (LPCONTEXTMENU3)*ppContextMenu;
}
else if (SUCCEEDED(icm1->QueryInterface(IID_IContextMenu2, ppContextMenu))) {
m_contextMenu2 = (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
}
};
ContextMenu contextMenu;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
HMODULE hmod = LoadLibraryW(L"shell32.dll");
BOOL(WINAPI * FileIconInit)(_In_ BOOL fRestoreCache);
FileIconInit = (BOOL(WINAPI*)(BOOL)) GetProcAddress(hmod, MAKEINTRESOURCEA(660));
if (FileIconInit) {
FileIconInit(TRUE);
}
hInst = hInstance;
WNDCLASSA wc = {};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = szWindowClass;
RegisterClassA(&wc);
HWND hWnd = CreateWindowExA(
0, szWindowClass, szTitle, 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;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
IContextMenu2* cm2 = contextMenu.getIContextMenu2();
IContextMenu3* cm3 = contextMenu.getIContextMenu3();
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) {
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
contextMenu.showContextMenu(filePath, hWnd, 100, 100);
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Upvotes: 0
Views: 242