How to display system context menu for multiple fi

2019-06-14 17:37发布

This question is related to my previous question.

I need to obtain IContextMenu* interface pointer for files in different directories (or even drives).

See my code, which is not working properly (e.g. the file properties dialog shows wrong information), because I provide wrong relative PIDLs (as mentioned in this answer).

int main() {
    CoInitialize(NULL);
    LPOLESTR pszFile = OLESTR("c:\\Windows\\notepad.exe");
    LPOLESTR pszFile2 = OLESTR("c:\\Windows\\System32\\notepad.exe");
    //LPOLESTR pszDir = OLESTR("c:\\Windows\\");
    LPITEMIDLIST pidl = NULL;
    LPITEMIDLIST pidl2 = NULL;
    LPITEMIDLIST pidlDir;
    LPCITEMIDLIST pidlItem;
    LPCITEMIDLIST pidlItem2;
    HRESULT hr;
    IShellFolder* pFolder;
    //IShellFolder* pDir;
    IShellFolder* pDesktop;
    IContextMenu* pContextMenu;
    HMENU hMenu;
    CMINVOKECOMMANDINFO cmi;
    TCHAR szTemp[256];
    hr = SHGetDesktopFolder(&pDesktop);
    if (FAILED(hr)) {
        CoUninitialize();
        return 0;
    }

    HWND wnd = ::CreateWindowA("STATIC", "dummy", WS_VISIBLE, 0, 0, 100, 100, NULL, NULL, NULL, NULL);

    /*hr = pDesktop->ParseDisplayName(wnd, NULL, pszDir, NULL, &pidlDir, NULL);
    if (FAILED(hr)) {
        goto clear;
    }

    hr = pDesktop->BindToObject(pidlDir, 0, IID_IShellFolder, (void**)&pDir);
        if (FAILED(hr)) {
            goto clear;
    }
    */

    hr = pDesktop->ParseDisplayName(wnd, NULL, pszFile, NULL, &pidl, NULL);
    if (FAILED(hr)) {
        goto clear;
    }

    hr = pDesktop->ParseDisplayName(wnd, NULL, pszFile2, NULL, &pidl2, NULL);
    if (FAILED(hr)) {
        goto clear;
    }

    hr = SHBindToParent(pidl, IID_IShellFolder, (void **)&pFolder, &pidlItem);
    if (FAILED(hr)) {
        goto clear;
    }
    pFolder->Release();

    hr = SHBindToParent(pidl2, IID_IShellFolder, (void **)&pFolder, &pidlItem2);
    if (FAILED(hr)) {
        goto clear;
    }

    LPCITEMIDLIST list[] = {pidlItem, pidlItem2};
    hr = pFolder->GetUIObjectOf(wnd, 2, (LPCITEMIDLIST *)list, IID_IContextMenu, NULL, (void **)&pContextMenu);
    pFolder->Release();
    if (SUCCEEDED(hr)) {
        hMenu = CreatePopupMenu();

        if (hMenu) {
            hr = pContextMenu->QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_EXPLORE);
            if (SUCCEEDED(hr)) {
                int idCmd = TrackPopupMenu(hMenu,
                                           TPM_LEFTALIGN | TPM_RETURNCMD | TPM_RIGHTBUTTON,
                                           1, 1, 0, wnd, NULL);

                if (idCmd) {
                    cmi.cbSize = sizeof(CMINVOKECOMMANDINFO);
                    cmi.fMask = 0;
                    cmi.hwnd = wnd;
                    cmi.lpVerb = MAKEINTRESOURCEA(idCmd-1);
                    cmi.lpParameters = NULL;
                    cmi.lpDirectory = NULL;
                    cmi.nShow = SW_SHOWNORMAL;
                    cmi.dwHotKey = 0;
                    cmi.hIcon = NULL;
                    hr = pContextMenu->InvokeCommand(&cmi);
                    if (!SUCCEEDED(hr)) {
                        wsprintf(szTemp, _T("InvokeCommand failed. hr=%lx"), hr);
                        MessageBox(0, szTemp, 0, 0);
                        PostQuitMessage(0);
                    }
                }
            }

            DestroyMenu(hMenu);
        }

        pContextMenu->Release();
    }

    MSG msg;
    BOOL bRet;

    while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            // Handle Error
        }
        else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

clear:

    pDesktop->Release();
    SHFree(pidl);
    SHFree(pidl2);
    CoUninitialize();
    return 0;
}

Maybe it is possible to achieve using SHCreateDefaultContextMenu or CDefFolderMenu_Create2 but I don't know how.

标签: c++ winapi com
1条回答
ら.Afraid
2楼-- · 2019-06-14 18:19

It is possible if you embed IExplorerBrowser UI object into your app, which is available since Windows Vista. The you can fill it with any PIDLs through IResultsFolder. Here is sample code, which is just slightly modified example from Win7 Platform SDK (C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\winui\shell\appplatform\ExplorerBrowserCustomContents\ExplorerBrowserCustomContents.sln)

There is definitely an another method using IShellView which works on Windows XP but I didn't find it anyway.

enter image description here

#define STRICT_TYPED_ITEMIDS
#include <windows.h>
#include <windowsx.h>
#include <shlobj.h>
#include <shellapi.h>
#include <shlwapi.h>
#include <propkey.h>
#include <new>
#include "resource.h"

#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

HINSTANCE g_hinst = 0;

UINT const KFD_SELCHANGE = WM_USER;

class CFillResultsOnBackgroundThread;

class CExplorerBrowserHostDialog : public IServiceProvider, public ICommDlgBrowser
{
public:
    CExplorerBrowserHostDialog() : _cRef(1), _hdlg(NULL), _peb(NULL), _fEnumerated(FALSE), _prf(NULL)
    {
    }

    HRESULT DoModal(HWND hwnd)
    {
        DialogBoxParam(g_hinst, MAKEINTRESOURCE(IDD_DIALOG1), hwnd, s_DlgProc, (LPARAM)this);
        return S_OK;
    }

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(CExplorerBrowserHostDialog, IServiceProvider),
            QITABENT(CExplorerBrowserHostDialog, ICommDlgBrowser),
            { 0 },
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    STDMETHODIMP_(ULONG) Release()
    {
        long cRef = InterlockedDecrement(&_cRef);
        if (!cRef)
            delete this;
        return cRef;
    }

    // IServiceProvider
    STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void **ppv)
    {
        HRESULT hr = E_NOINTERFACE;
        *ppv = NULL;
        if (guidService == SID_SExplorerBrowserFrame)
        {
            // responding to this SID allows us to hook up our ICommDlgBrowser
            // implementation so we get selection change events from the view
            hr = QueryInterface(riid, ppv);
        }
        return hr;
    }

    // ICommDlgBrowser
    STDMETHODIMP OnDefaultCommand(IShellView * /* psv */)
    {
        _OnExplore();
        return S_OK;
    }

    STDMETHODIMP OnStateChange(IShellView * /* psv */, ULONG uChange)
    {
        if (uChange == CDBOSC_SELCHANGE)
        {
            PostMessage(_hdlg, KFD_SELCHANGE, 0, 0);
        }
        return S_OK;
    }

    STDMETHODIMP IncludeObject(IShellView * /* psv */, PCUITEMID_CHILD /* pidl */)
    {
        return S_OK;
    }

    void FillResultsOnBackgroundThread(IResultsFolder *prf);

private:
    ~CExplorerBrowserHostDialog()
    {
    }

    static INT_PTR CALLBACK s_DlgProc(HWND hdlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        CExplorerBrowserHostDialog *pebhd = reinterpret_cast<CExplorerBrowserHostDialog *>(GetWindowLongPtr(hdlg, DWLP_USER));
        if (uMsg == WM_INITDIALOG)
        {
            pebhd = reinterpret_cast<CExplorerBrowserHostDialog *>(lParam);
            pebhd->_hdlg = hdlg;
            SetWindowLongPtr(hdlg, DWLP_USER, reinterpret_cast<LONG_PTR>(pebhd));
        }
        return pebhd ? pebhd->_DlgProc(uMsg, wParam, lParam) : 0;
    }

    INT_PTR _DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    HRESULT _FillViewWithKnownFolders(IResultsFolder *prf);
    void _OnInitDlg();
    void _OnDestroyDlg();
    void _StartFolderEnum();
    void _OnSelChange();
    void _OnExplore();
    void _OnRefresh();

    long _cRef;
    HWND _hdlg;

    IExplorerBrowser *_peb;
    IResultsFolder *_prf;
    BOOL _fEnumerated;
    static const UINT c_rgControlsShownOnEnum[3];  // controls that will be shown while known folder list is populated
    static const UINT c_rgControlsHiddenOnEnum[4]; // controls that will be hidden while known folder list is populated
};

const UINT CExplorerBrowserHostDialog::c_rgControlsShownOnEnum[] =
{
    IDC_STATUS,
    IDC_ENUMNAME,
    IDC_ENUMPATH
};

const UINT CExplorerBrowserHostDialog::c_rgControlsHiddenOnEnum[] =
{
    IDC_FOLDERNAME,
    IDC_FOLDERPATH,
    IDC_LBLFOLDER,
    IDC_LBLPATH
};

HRESULT CExplorerBrowserHostDialog::_FillViewWithKnownFolders(IResultsFolder *prf)
{
    LPOLESTR pszFile = OLESTR("c:\\Windows\\notepad.exe");
    LPOLESTR pszFile2 = OLESTR("c:\\Windows\\System32\\notepad.exe");
    IShellFolder* pDesktop;
    PIDLIST_RELATIVE pidl = NULL;
    PIDLIST_RELATIVE pidl2 = NULL;
    HRESULT hr;

    hr = SHGetDesktopFolder(&pDesktop);
    if (FAILED(hr)) {
        return E_FAIL;
    }

    hr = pDesktop->ParseDisplayName(0, NULL, pszFile, NULL, &pidl, NULL);
    if (FAILED(hr)) {
        pDesktop->Release();
        return E_FAIL;
    }

    hr = pDesktop->ParseDisplayName(0, NULL, pszFile2, NULL, &pidl2, NULL);
    if (FAILED(hr)) {
        pDesktop->Release();
        return E_FAIL;
    }
    prf->AddIDList((PIDLIST_ABSOLUTE)pidl, 0);
    prf->AddIDList((PIDLIST_ABSOLUTE)pidl2, 0);
    pDesktop->Release();
    IFolderView2 *pfv2;
    IShellView *shellView;
    hr = _peb->GetCurrentView(IID_PPV_ARGS(&pfv2));
    if (SUCCEEDED(hr)) {
        pfv2->SelectItem(0, SVSI_SELECT);
        pfv2->SelectItem(1, SVSI_SELECT);
        //hr = pfv2->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
        hr = _peb->GetCurrentView(IID_PPV_ARGS(&shellView));
        if (SUCCEEDED(hr)) {
            HWND wnd = 0;
            shellView->GetWindow(&wnd);
            //SendMessage(wnd, WM_CONTEXTMENU, 0, MAKELPARAM(0,0)); not working
        }
    }

    return S_OK;
}

void CExplorerBrowserHostDialog::_OnInitDlg()
{
    // Hide initial folder information
    for (UINT i = 0; i < ARRAYSIZE(c_rgControlsHiddenOnEnum); i++)
    {
        ShowWindow(GetDlgItem(_hdlg, c_rgControlsHiddenOnEnum[i]), SW_HIDE);
    }

    HWND hwndStatic = GetDlgItem(_hdlg, IDC_BROWSER);
    if (hwndStatic)
    {
        RECT rc;
        GetWindowRect(hwndStatic, &rc);
        MapWindowRect(HWND_DESKTOP, _hdlg, &rc);

        HRESULT hr = CoCreateInstance(CLSID_ExplorerBrowser, NULL, CLSCTX_INPROC, IID_PPV_ARGS(&_peb));
        if (SUCCEEDED(hr))
        {
            IUnknown_SetSite(_peb, static_cast<IServiceProvider *>(this));

            FOLDERSETTINGS fs = {0};
            fs.ViewMode = FVM_DETAILS;
            fs.fFlags = FWF_AUTOARRANGE | FWF_NOWEBVIEW;
            hr = _peb->Initialize(_hdlg, &rc, &fs);
            if (SUCCEEDED(hr))
            {
                _peb->SetOptions(EBO_NAVIGATEONCE); // do not allow navigations

                // Initialize the explorer browser so that we can use the results folder
                // as the data source. This enables us to program the contents of
                // the view via IResultsFolder

                hr = _peb->FillFromObject(NULL, EBF_NODROPTARGET);
                if (SUCCEEDED(hr))
                {
                    IFolderView2 *pfv2;
                    hr = _peb->GetCurrentView(IID_PPV_ARGS(&pfv2));
                    if (SUCCEEDED(hr))
                    {
                        IColumnManager *pcm;
                        hr = pfv2->QueryInterface(IID_PPV_ARGS(&pcm));
                        if (SUCCEEDED(hr))
                        {
                            PROPERTYKEY rgkeys[] = {PKEY_ItemNameDisplay, PKEY_ItemFolderPathDisplay};
                            hr = pcm->SetColumns(rgkeys, ARRAYSIZE(rgkeys));
                            if (SUCCEEDED(hr))
                            {
                                CM_COLUMNINFO ci = {sizeof(ci), CM_MASK_WIDTH | CM_MASK_DEFAULTWIDTH | CM_MASK_IDEALWIDTH};
                                hr = pcm->GetColumnInfo(PKEY_ItemFolderPathDisplay, &ci);
                                if (SUCCEEDED(hr))
                                {
                                    ci.uWidth += 100;
                                    ci.uDefaultWidth += 100;
                                    ci.uIdealWidth += 100;
                                    pcm->SetColumnInfo(PKEY_ItemFolderPathDisplay, &ci);
                                }
                            }
                            pcm->Release();
                        }

                        hr = pfv2->GetFolder(IID_PPV_ARGS(&_prf));
                        if (SUCCEEDED(hr))
                        {
                            _StartFolderEnum();
                        }
                        pfv2->Release();
                    }
                }
            }
        }
        // If we fail to initialize properly, close the dialog
        if (FAILED(hr))
        {
            EndDialog(_hdlg, IDCLOSE);
        }
    }
}

// pass -1 for the current selected item
// returns an IShellItem type object

HRESULT GetItemFromView(IFolderView2 *pfv, int iItem, REFIID riid, void **ppv)
{
    *ppv = NULL;

    HRESULT hr = S_OK;

    if (iItem == -1)
    {
        hr = pfv->GetSelectedItem(-1, &iItem); // Returns S_FALSE if none selected
    }

    if (S_OK == hr)
    {
        hr = pfv->GetItem(iItem, riid, ppv);
    }
    else
    {
        hr = E_FAIL;
    }
    return hr;
}

void CExplorerBrowserHostDialog::_OnSelChange()
{
    if (_fEnumerated)
    {
        IFolderView2 *pfv2;
        HRESULT hr = _peb->GetCurrentView(IID_PPV_ARGS(&pfv2));
        if (SUCCEEDED(hr))
        {
            IShellItem2 *psi;
            hr = GetItemFromView(pfv2, -1, IID_PPV_ARGS(&psi));
            if (SUCCEEDED(hr))
            {
                PWSTR pszName;
                hr = psi->GetDisplayName(SIGDN_NORMALDISPLAY, &pszName);
                if (SUCCEEDED(hr))
                {
                    SetDlgItemText(_hdlg, IDC_FOLDERNAME, pszName);
                    CoTaskMemFree(pszName);
                }

                hr = psi->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pszName);
                if (SUCCEEDED(hr))
                {
                    SetDlgItemText(_hdlg, IDC_FOLDERPATH, pszName);
                    CoTaskMemFree(pszName);
                }

                psi->Release();
            }
            else if (hr == S_FALSE)
            {
                SetDlgItemText(_hdlg, IDC_FOLDERNAME, TEXT(""));
                SetDlgItemText(_hdlg, IDC_FOLDERPATH, TEXT(""));
            }

            EnableWindow(GetDlgItem(_hdlg, IDC_EXPLORE), hr == S_OK);

            pfv2->Release();
        }
    }
}

void CExplorerBrowserHostDialog::_OnDestroyDlg()
{
    if (_peb)
    {
        IUnknown_SetSite(_peb, NULL);
        _peb->Destroy();
        _peb->Release();
        _peb = NULL;
    }

    if (_prf)
    {
        _prf->Release();
        _prf = NULL;
    }
}

void CExplorerBrowserHostDialog::_OnExplore()
{
    IFolderView2 *pfv2;
    HRESULT hr = _peb->GetCurrentView(IID_PPV_ARGS(&pfv2));
    if (SUCCEEDED(hr))
    {
        IShellItem *psi;
        hr = GetItemFromView(pfv2, -1, IID_PPV_ARGS(&psi));
        if (SUCCEEDED(hr))
        {
            PIDLIST_ABSOLUTE pidl;
            hr = SHGetIDListFromObject(psi, &pidl);
            if (SUCCEEDED(hr))
            {
                SHELLEXECUTEINFO ei = { sizeof(ei) };
                ei.fMask        = SEE_MASK_INVOKEIDLIST;
                ei.hwnd         = _hdlg;
                ei.nShow        = SW_NORMAL;
                ei.lpIDList     = pidl;

                ShellExecuteEx(&ei);

                CoTaskMemFree(pidl);
            }
            psi->Release();
        }
        pfv2->Release();
    }
}

void CExplorerBrowserHostDialog::_OnRefresh()
{
    _fEnumerated = FALSE;

    // Update UI
    EnableWindow(GetDlgItem(_hdlg, IDC_EXPLORE), FALSE);
    EnableWindow(GetDlgItem(_hdlg, IDC_REFRESH), FALSE);

    if (SUCCEEDED(_peb->RemoveAll()))
    {
        _StartFolderEnum();
    }
}

void CExplorerBrowserHostDialog::_StartFolderEnum()
{
    FillResultsOnBackgroundThread(_prf);
}

void CExplorerBrowserHostDialog::FillResultsOnBackgroundThread(IResultsFolder *prf)
{
    _FillViewWithKnownFolders(prf);

    _fEnumerated = TRUE;
/*
    // Adjust dialog to show proper view info and buttons
    for (UINT k = 0; k < ARRAYSIZE(c_rgControlsShownOnEnum); k++)
    {
        ShowWindow(GetDlgItem(_hdlg, c_rgControlsShownOnEnum[k]), SW_HIDE);
    }
    for (UINT l = 0; l < ARRAYSIZE(c_rgControlsHiddenOnEnum); l++)
    {
        ShowWindow(GetDlgItem(_hdlg, c_rgControlsHiddenOnEnum[l]), SW_SHOW);
    }*/
    EnableWindow(GetDlgItem(_hdlg, IDC_REFRESH), TRUE);
}

INT_PTR CExplorerBrowserHostDialog::_DlgProc(UINT uMsg, WPARAM wParam, LPARAM /* lParam */)
{
    INT_PTR iRet = 1;   // default for all handled cases in switch below

    switch (uMsg)
    {
    case WM_INITDIALOG:
        _OnInitDlg();
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
        case IDCANCEL:
        case IDC_CANCEL:
            return EndDialog(_hdlg, TRUE);

        case IDC_REFRESH:
            _OnRefresh();
            break;

        case IDC_EXPLORE:
            _OnExplore();
            break;
        }
        break;

    case KFD_SELCHANGE:
        _OnSelChange();
        break;

    case WM_DESTROY:
        _OnDestroyDlg();
        break;

    default:
        iRet = 0;
        break;
    }
    return iRet;
}

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int)
{
    g_hinst = hInstance;

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        OleInitialize(0);   // for drag and drop

        CExplorerBrowserHostDialog *pdlg = new (std::nothrow) CExplorerBrowserHostDialog();
        if (pdlg)
        {
            pdlg->DoModal(NULL);
            pdlg->Release();
        }

        OleUninitialize();
        CoUninitialize();
    }

    return 0;
}
查看更多
登录 后发表回答