c++windowswinapiwindows-shell

Missing context menu icon after IContextMenu3::QueryContextMenu was called


I am trying to display a context menu for a given folder and for this I have implemented the ContextMenu class which you can see below.

During the implementation I encountered a problem with displaying one of the icons for the "Send to" submenu.

After I display the context menu for a folder, expand the list of submenus for "Send to", the submenu called "Bluetooth device" does not have an icon.

However, when I display the context menu for the second time, this icon appears.

My question is, how can I make the icon for the "Bluetooth device" submenu show immediately when the context menu is displayed for the first time?

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

Again, when I press "Context menu" for the first time, the icon for "Send to" -> "Bluetooth device" does not show. But if you click "Context menu" a second time, the icon will appear. enter image description here

Also, as you can see on screenshot, "Properties" menu is missing, don`t understand why.

#include <windows.h>
#include <shlobj.h>
#include <string>

HINSTANCE hInst;
LPCSTR szTitle = "WinAPI";
LPCSTR szWindowClass = "MYWINDOWCLASS";
const wchar_t* filePath = L"C:\\Users\\Username\\Folder";

class ContextMenu
{
public:
    IContextMenu3* getIContextMenu()
    {
        return m_contextMenu;
    }
    void showContextMenu(const std::wstring& path, HWND hwnd, UINT xPos, UINT yPos)
    {
        IContextMenu* pcm = nullptr;

        if (SUCCEEDED(GetUIObjectOfFile(hwnd, path.c_str(), IID_IContextMenu, (void**)&pcm))) {

            if (SUCCEEDED(pcm->QueryInterface(IID_IContextMenu3, (void**)&m_contextMenu))) {

                HMENU hmenu = CreatePopupMenu();

                if (hmenu) {

                    if (SUCCEEDED(m_contextMenu->QueryContextMenu(hmenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) {
                        TrackPopupMenuEx(hmenu, TPM_RETURNCMD, xPos, yPos, (HWND)hwnd, NULL);
                        DestroyMenu(hmenu);
                        m_contextMenu->Release();
                        m_contextMenu = nullptr;
                    }
                }

                pcm->Release();
            }
        }
    }

    void showFolderBackgroundContextMenu(const std::wstring& path, HWND hwnd, UINT xPos, UINT yPos)
    {
        IContextMenu* pcm = nullptr;

        if (SUCCEEDED(GetUIObjectOfFolder(hwnd, path.c_str(), IID_IContextMenu, (void**)&pcm))) {

            if (SUCCEEDED(pcm->QueryInterface(IID_IContextMenu3, (void**)&m_contextMenu))) {

                HMENU hmenu = CreatePopupMenu();

                if (hmenu) {

                    if (SUCCEEDED(m_contextMenu->QueryContextMenu(hmenu, 0, SCRATCH_QCM_FIRST, SCRATCH_QCM_LAST, CMF_NORMAL))) {
                        TrackPopupMenuEx(hmenu, TPM_RETURNCMD, xPos, yPos, (HWND)hwnd, NULL);
                        DestroyMenu(hmenu);
                        m_contextMenu->Release();
                        m_contextMenu = nullptr;
                    }
                }

                pcm->Release();
            }
        }
    }

private:
    const int SCRATCH_QCM_FIRST = 1;
    const int SCRATCH_QCM_LAST = 0x7FFF;
    IContextMenu3* m_contextMenu = nullptr;

    HRESULT GetUIObjectOfFile(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->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv);
                psf->Release();
            }
            CoTaskMemFree(pidl);
        }

        return hr;
    }

    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;
    }
};

ContextMenu contextMenu;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    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) {
    if (contextMenu.getIContextMenu()) {
        LRESULT res;
        contextMenu.getIContextMenu()->HandleMenuMsg2(message, wParam, lParam, &res);

    }
    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) {
            contextMenu.showContextMenu(filePath,hWnd, 100, 100);
        }
        break;
    }

    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Solution

  • before call QueryContextMenu you need call FileIconInit

    If you are using system image lists in your own process, you must call FileIconInit at the following times:

    • On launch.
    • In response to a WM_SETTINGCHANGE message when the SPI_SETNONCLIENTMETRICS flag is set.
        if (HMODULE hmod = LoadLibraryW(L"shell32.dll"))
        {
            union {
                BOOL (WINAPI * FileIconInit)(_In_ BOOL fRestoreCache);
                PVOID pv;
            };
    
            if (pv = GetProcAddress(hmod, (PCSTR)660))
            {
                FileIconInit(TRUE);
            }
        }