Joe J
Joe J

Reputation: 359

Right-click background menu shows broken items

I am trying to display a right-click background context menu for a folder.

The menu is displayed, but there are the following problems:

  1. 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)

  2. Despite calling HandleMenuMsg and HandleMenuMsg2, the submenus for "Give access to" are not shown

  3. 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:

  1. Replace the filePath value with the path to the folder
  2. Press the "Context menu" button to display the context menu

#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

Answers (0)

Related Questions