Search code examples
c++winapicom

Display file properties dialog for files in different directories


I'm trying to open the default file properties dialog but unable to get it working property for files in different directories. I need to obtain IContextMenu* interface pointer for files in different directories (or even drives). I expect to see "Various folders" in the properties dialog.

This is my naive approach:

int main() {
    CoInitialize(NULL);
    LPOLESTR pszFile = OLESTR("c:\\Windows\\notepad.exe");
    LPOLESTR pszFile2 = OLESTR("c:\\Windows\\System32\\notepad.exe");
    LPITEMIDLIST pidl;
    LPITEMIDLIST pidl2;
    LPCITEMIDLIST pidlItem;
    LPCITEMIDLIST pidlItem2;
    HRESULT hr;
    IShellFolder* pFolder;
    IContextMenu* pContextMenu;
    CMINVOKECOMMANDINFO cmi;

    hr = SHGetDesktopFolder(&pFolder);
    if (FAILED(hr)) return 0;

    hr = pFolder->ParseDisplayName(HWND_DESKTOP, NULL, pszFile, NULL, &pidl, NULL);
    hr = pFolder->ParseDisplayName(HWND_DESKTOP, NULL, pszFile2, NULL, &pidl2, NULL);
    pFolder->Release();
    if (FAILED(hr)) return 0;

    hr = SHBindToParent(pidl, IID_IShellFolder, (void **)&pFolder, &pidlItem);
    if (FAILED(hr)) {
        SHFree(pidl);
        return 0;
    }
    //pFolder->Release();
    hr = SHBindToParent(pidl2, IID_IShellFolder, (void **)&pFolder, &pidlItem2);
    if (FAILED(hr)) {
        SHFree(pidl2);
        return 0;
    }

    LPCITEMIDLIST list[] = {pidlItem, pidlItem2};
    hr = pFolder->GetUIObjectOf(HWND_DESKTOP, 2, (LPCITEMIDLIST *)list, IID_IContextMenu, NULL, (void **)&pContextMenu);
    pFolder->Release();
    if (SUCCEEDED(hr)) {
        ZeroMemory(&cmi, sizeof(cmi));
        cmi.cbSize = sizeof(cmi);
        cmi.lpVerb = "properties";
        cmi.nShow = SW_SHOWNORMAL;

        hr = pContextMenu->InvokeCommand(&cmi);
        MessageBox(0, _T("Dummy message box"), 0, 0);
        Sleep(10000); // Give the system time to show the dialog before exiting
        pContextMenu->Release();
    }

    SHFree(pidl);

    return 0;
}

Also I tried this, but it also not working at all. Also I tried to use desktop IShellFolder as parent of my PIDLs, but it also not working.

Any ideas?

screenshot 2015-12-31 002.png

screenshot 2015-12-31 001.png


Solution

  • IShellFolder::GetUIObjectOf() only works with single-level PIDLs that are relative to the IShellFolder being queried. This is clearly stated in the GetUIObjectOf() documentation:

    The address of an array of pointers to ITEMIDLIST structures, each of which uniquely identifies a file object or subfolder relative to the parent folder. Each item identifier list must contain exactly one SHITEMID structure followed by a terminating zero.

    You are converting the 2 absolute PIDLs into relative PIDLs of their respective folders, but then you are using the IShellFolder of the Windows\System32 folder to retrieve the IContextMenu for both files. That will not work for the relative PIDL belonging to the Windows\ folder, since the IShellFolder of the Windows\System32 folder only knows about files in the Windows\System32 folder.

    Various online examples show the IContextMenu being queried from the IShellFolder of the desktop when multiple files are involved. That approach only works when the files are in the same parent folder (such as demonstrated in Raymond Chen's example). Things get more complicated when the files are in different folders.

    You also have a few memory leaks in your code.

    The correct way to handle this situation is to use the SHMultiFileProperties() function:

    Displays a merged property sheet for a set of files. Property values common to all the files are shown while those that differ display the string (multiple values).

    Use the IShellFolder of the desktop to get an IDataObject for the absolute PIDLs (this is one time where GetUIObjectOf() is allowed to violate the "Each item identifier list must contain exactly one SHITEMID structure" rule) and then pass that to SHMultiFileProperties(). For example:

    int main()
    {
        CoInitialize(NULL);
    
        LPOLESTR pszFile = OLESTR("c:\\Windows\\notepad.exe");
        LPOLESTR pszFile2 = OLESTR("c:\\Windows\\System32\\notepad.exe");
        LPITEMIDLIST pidl;
        LPITEMIDLIST pidl2;
        HRESULT hr;
        IShellFolder* pDesktop;
        IDataObject *pDataObject;
    
        hr = SHGetDesktopFolder(&pDesktop);
        if (FAILED(hr))
        {
            CoUninitialize();
            return 0;
        }
    
        hr = pDesktop->ParseDisplayName(HWND_DESKTOP, NULL, pszFile, NULL, &pidl, NULL);
        if (FAILED(hr)) {
            pDesktop->Release();
            CoUninitialize();
            return 0;
        }
    
        hr = pDesktop->ParseDisplayName(HWND_DESKTOP, NULL, pszFile2, NULL, &pidl2, NULL);
        if (FAILED(hr)) {
            SHFree(pidl);
            pDesktop->Release();
            CoUninitialize();
            return 0;
        }
    
        LPCITEMIDLIST list[] = {pidl, pidl2};
        hr = pDesktop->GetUIObjectOf(HWND_DESKTOP, 2, (LPCITEMIDLIST *)list, IID_IDataObject, NULL, (void **)&pDataObject);
        // alternatively, you can also use SHCreateDataObject() or CIDLData_CreateFromIDArray() to create the IDataObject
        pDesktop->Release();
        SHFree(pidl);
        SHFree(pidl2);
    
        if (SUCCEEDED(hr)) {
            hr = SHMultiFileProperties(pDataObject, 0);
            pDataObject->Release();
    
            if (SUCCEEDED(hr)) {
                MessageBox(0, _T("Dummy message box"), 0, 0);
                Sleep(10000); // Give the system time to show the dialog before exiting
            }
        }
    
        CoUninitialize();
        return 0;
    }