【问题标题】:How do you keep Properties Dialog open while Closing the App that opened it?在关闭打开它的应用程序时如何保持属性对话框打开?
【发布时间】:2020-02-19 04:49:06
【问题描述】:

我正在做一些可以拉出上下文菜单的东西。我希望能够关闭应用程序,同时保留“属性”等打开的内容。发生的情况是,如果您打开上下文菜单,选择“属性”,它会打开属性对话框并很好地关闭上下文菜单。但是当我关闭我的应用程序时,属性窗口也会关闭。

问题是:如何保持窗口打开?如果你不能,应用程序如何判断它是否有一个依赖于它打开的窗口/对话框? EnumWindows 不显示它; Spy++ 显示属性对话框也独立存在(在桌面下)。

CComPtr<IContextMenu> pMenu;
CComPtr<IShellFolder> pFolder;
PCUITEMID_CHILD pidl = NULL;
SHBindToParent(item.pidl, IID_IShellFolder, (void**)&pFolder, &pidl);
pFolder->GetUIObjectOf(NULL, 1, &pidl, IID_IContextMenu, NULL, (void**)&pMenu);
UINT flags = CMF_NORMAL | CMF_CANRENAME;
pMenu->QueryContextMenu(menu, CID_FIRST, CID_LAST, 0x7fff, flags);
.
.
.
// command invocation
CMINVOKECOMMANDINFOEX info;
memset(&info, 0, sizeof(info));
info.cbSize = sizeof(info);
info.fMask = CMIC_MASK_NOASYNC | CMIC_MASK_PTINVOKE; // I've played around with flags
info.hwnd = toolbar.m_hWnd; // I've played around with this value
info.lpVerb = MAKEINTRESOURCEA(cid - CID_LAST);
info.nShow = SW_SHOWNORMAL;
info.ptInvoke = point;
pMenu->InvokeCommand((CMINVOKECOMMANDINFO*)&info);  

TIA!!

【问题讨论】:

  • shell 属性对话框在进程中运行,因此会在进程退出时关闭。将动词设置为L"properties" 时使用ShellExecuteEx() 可能会起作用。

标签: shell winapi com


【解决方案1】:

属性对话框正在进行中。

您需要致电SHSetInstanceExplorerkeep your process alive until that COM object is released

int main()
{
    struct CoInit { HRESULT m_hr; CoInit() { m_hr = CoInitialize(0); } ~CoInit() { if (SUCCEEDED(m_hr)) CoUninitialize(); } } coinit;
    ProcessReference ref;
    IShellItem*pSI;
    HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_Windows, KF_FLAG_DEFAULT, L"Explorer.exe", IID_IShellItem, (void**) &pSI);
    if (hr) return hr;
    IContextMenu*pCM;
    hr = pSI->BindToHandler(NULL, BHID_SFUIObject, IID_IContextMenu, (void**) &pCM);
    pSI->Release();
    if (hr) return hr;
    //HMENU menu = CreatePopupMenu();
    //pCM->QueryContextMenu(menu, 0, 1, 0x7fff, CMF_NORMAL);
    CMINVOKECOMMANDINFO info;
    memset(&info, 0, sizeof(info));
    info.cbSize = sizeof(info);
    info.fMask = CMIC_MASK_NOASYNC; // I've played around with flags
    info.hwnd = NULL; // I've played around with this value
    info.lpVerb = "Properties";
    info.nShow = SW_SHOWNORMAL;
    //info.ptInvoke = point;
    hr = pCM->InvokeCommand((CMINVOKECOMMANDINFO*)&info);
    pCM->Release();
    return hr;
}

【讨论】:

  • 测试,我发现使用 -&gt;InvokeCommand() 和 SHSetInstanceExplorer 仍然会终止(销毁时引用计数为 1 - 使用来自第二个链接的类)。但是使用ShellExecuteEx() 它确实有效(销毁时计数为2,关闭属性然后允许终止)。除非我错过了什么?
  • 您是否在程序开始时调用 SHSetInstanceExplorer? 1 表示对象仍然存在,您必须等待它变为 0,就像我链接到的示例中一样。
  • 是的,我的意思是在发布我的参考之前它是 1。 ShellExecuteEx() 有效,它在发布前是 2,所以它确实有效,只是不适用于 -&gt;InvokeCommand。使用该链接中指示的相同帮助程序类。
  • 您不应该释放任何东西,当 ProcessReference 类超出范围时会为您执行此操作。
  • 我指的是那个。我放了一个断点并检查值。
【解决方案2】:

EnumWindows 不显示它

如果我误解了什么,请告诉我。

这是我的测试:

第二张截图是我用EnumWindows得到的,在我看来,对于一些APP Properties Dialog,你可以用EnumWindows得到它。

同时,正如@Jonathan Potter 所说,shell 属性对话框是在进程中运行的,您可以使用ShellExecuteEx 独立打开属性对话框。

仅作为补充,如果你只是想独立打开属性对话框,可以使用上面的api。这样,当你关闭应用程序时,你可以重新打开属性对话框,如果你不打扰。

最小代码示例:

#include <windows.h>
#include <shlobj_core.h>

#pragma comment(lib,"Shell32.lib")

class ProcessReference : public IUnknown {
public:
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
    {
        if (riid == IID_IUnknown) {
            *ppv = static_cast<IUnknown*>(this);
            AddRef();
            return S_OK;
        }
        *ppv = NULL; return E_NOINTERFACE;
    }
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }
    STDMETHODIMP_(ULONG) Release()
    {
        LONG lRef = InterlockedDecrement(&m_cRef);
        if (lRef == 0) PostThreadMessage(m_dwThread, WM_NULL, 0, 0);
        return lRef;
    }
    ProcessReference()
        : m_cRef(1), m_dwThread(GetCurrentThreadId())
    {
        SHSetInstanceExplorer(this);
    }
    ~ProcessReference()
    {
        SHSetInstanceExplorer(NULL);
        Release();
        MSG msg;
        while (m_cRef && GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
private:
    LONG m_cRef;
    DWORD m_dwThread;
};

int main(void)
{
    struct CoInit { HRESULT m_hr; CoInit() { m_hr = CoInitialize(0); } ~CoInit() { if (SUCCEEDED(m_hr)) CoUninitialize(); } } coinit;
    ProcessReference ref;
    IShellItem*pSI;
    HRESULT hr = SHCreateItemInKnownFolder(FOLDERID_Windows, KF_FLAG_DEFAULT, L"Explorer.exe", IID_IShellItem, (void**)&pSI);
    if (hr) return hr;
    IContextMenu*pCM;
    hr = pSI->BindToHandler(NULL, BHID_SFUIObject, IID_IContextMenu, (void**)&pCM);
    pSI->Release();
    if (hr) return hr;

    SHELLEXECUTEINFO info = { 0 };

    info.cbSize = sizeof info;
    info.lpFile = "C:\\Users\\strives\\Desktop\\print.txt";
    info.nShow = SW_SHOW;
    info.fMask = SEE_MASK_INVOKEIDLIST;
    info.lpVerb = "properties";

    ShellExecuteEx(&info);


}

更新:

SHELLEXECUTEINFO info = { 0 };

info.cbSize = sizeof info;
info.lpFile = "C:\\Users\\strives\\Desktop\\Test.txt";
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
info.lpVerb = "openas";

ShellExecuteEx(&info);

调试:

【讨论】:

  • Sleep 是一个丑陋的黑客,对此有正确的解决方案。
  • EnumWindows 没有发现它是应用程序窗口的依赖项(子项)。不知道为什么会有睡眠,但安德斯似乎说它是必要的,而是另一种方式来做任何需要的事情。如果有指示会有所帮助。谢谢大家!稍后我会做一些测试。
  • 您还需要SHSetInstanceExplorer(),否则它仍然会关闭属性。这就是安德烈斯一定提到的。 devblogs.microsoft.com/oldnewthing/20080528-00/?p=22163 的“在该 COM 对象被释放之前保持您的进程处于活动状态”链接
  • 我还要指出,对于上下文菜单,虽然它适用于属性,ShellExecuteEx 不适用于其他项目,比如选择“使用记事本++ 编辑”,而是打开相关应用程序中的文件。但是InvokeCommand() 方法确实有效。它只是不支持SHSetInstanceExplorer。是否有一个支持一切的,或者我将不得不 _stricmp() 一堆动词?
  • @user3161924 感谢您的提醒,我已更正。另外,据我所知,使用ShellExecuteEx只能实现“打开方式”这一步。
猜你喜欢
  • 1970-01-01
  • 2015-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-10
  • 1970-01-01
相关资源
最近更新 更多