【问题标题】:Display file properties dialog for files in different directories显示不同目录中文件的文件属性对话框
【发布时间】:2016-04-05 14:43:03
【问题描述】:

我正在尝试打开默认文件属性对话框,但无法为其在不同目录中的文件设置工作属性。 我需要为不同目录(甚至驱动器)中的文件获取IContextMenu* 接口指针。 我希望在属性对话框中看到“各种文件夹”。

这是我幼稚的做法:

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

我也试过this,但它也根本不起作用。 我还尝试使用桌面 IShellFolder 作为我的 PIDL 的父级,但它也无法正常工作。

有什么想法吗?

【问题讨论】:

    标签: c++ winapi com


    【解决方案1】:

    IShellFolder::GetUIObjectOf() 仅适用于与被查询的IShellFolder 相关的单级 PIDL。这在GetUIObjectOf() documentation中有明确说明:

    指向 ITEMIDLIST 结构的指针数组的地址,每个结构唯一地标识相对于父文件夹的文件对象或子文件夹。每个项目标识符列表必须包含一个 SHITEMID 结构,后跟一个终止零

    您正在将 2 个绝对 PIDL 转换为其各自文件夹的相对 PIDL,但随后您使用 Windows\System32 文件夹的 IShellFolder 来检索这两个文件的 IContextMenu。这不适用于属于Windows\ 文件夹的相对PIDL,因为Windows\System32 文件夹的IShellFolder 只知道Windows\System32 文件夹中的文件。

    各种在线示例显示当涉及多个文件时,从桌面的IShellFolder 查询IContextMenu。该方法仅在文件位于同一父文件夹中时才有效(例如在Raymond Chen's example 中演示)。当文件位于不同的文件夹中时,事情会变得更加复杂。

    您的代码中还有一些内存泄漏。

    处理这种情况的正确方法是使用SHMultiFileProperties()函数:

    显示一组文件的合并属性表。显示所有文件共有的属性值,而不同的则显示字符串(多个值)。

    使用桌面的IShellFolder 获取绝对PIDL 的IDataObject(这是允许GetUIObjectOf() 违反“每个项目标识符列表必须只包含一个SHITEMID 结构”规则的一次)和然后将其传递给SHMultiFileProperties()。例如:

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

    【讨论】:

    • 这是否意味着无法使用IContextMenu 归档相同的行为?据我了解,文件管理器分别处理菜单项“属性”并执行SHMultiFileProperties,而不是用于所有其他菜单项的IContextMenu::InvokeCommand?但是正在显示的菜单功能齐全,您可以复制,剪切,删除选定的文件,即使它们位于不同的目录中。
    • 可以通过IContextMenu 实现,但并不容易。 Raymond Chen 说:Get IContextMenu for files in diferent folders你必须编写自己的复合上下文菜单。这就是 Find Files 所做的......这并不容易。你必须为你的组中的每个对象 GetUIObjectOf(IContextMenu),然后取所有菜单项的交集并将其作为组合的上下文菜单。或者,如果有多个不同文件类型的文件,您可以执行 Find Files 所做的操作。"
    • 我怀疑编写“复合上下文菜单”是否可以调用“属性”对话框。在实现相同动词的文件上调用非可视操作时会更有意义。您只需遍历收集的 IContextMenu 对象,在每个对象上调用所需的动词。
    • 有趣的观察发现,Total Commander 的属性对话框对于不同驱动器上的文件无法正常工作(缺少“常规”选项卡)。这意味着 TotalCmd 不使用 SHMultiFileProperties 函数。当我使用 IContextMenu 编写测试代码并使用 CSIDL_DRIVES 或类似的东西作为我的 PIDL 的父级时,我遇到了同样的问题。
    • 您可以尝试使用CDefFolderMenu_Create2()(及其相关表亲SHCreateDefaultContextMenu())为跨多个文件夹的文件获取IContextMenu。有关此主题的讨论,请参阅 The multi-folder shell context menu riddle
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-13
    • 1970-01-01
    相关资源
    最近更新 更多