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:
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.
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;
}
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 theSPI_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);
}
}