在现有 Shell 窗口的新选项卡中打开文件夹

问题描述 投票:0回答:1

注意:此问题仅适用于Windows 11。

我想在现有 shell 窗口的新选项卡中打开一个文件夹。好像没有官方API。

我尝试过两种方法:

第一个,也是最明显的一个,是带有动词

ShellExecute
opennewtab
函数(该动词来自注册表中文件夹 ProgID 的 Shell 键 -
[HKEY_CLASSES_ROOT\Folder\shell\opennewtab]
)。但是,这没有用。该功能只是为文件夹打开一个新窗口

第二次机会,我检查了注册表中的

opennewtab
动词。它作为
IExecuteCommand
实现(通过
DelegateExecute
)。因此,我的第二次尝试是从其 COM 服务器检索接口,通过它们提供的方法(
SetXXX
IExecuteCommand
函数系列、
Initialize
IInitializeCommand
方法等)设置所需的参数。 ),最后调用
Execute
方法。

为了了解 Windows Shell 传递给这些方法的内容以及调用它们的顺序,我实现了自定义动词实现及其 COM 服务器,并用我的服务器替换了用 opennewtab 注册的旧

COM
服务器。

在这里您可以看到 DebugView 显示的输出(我的动词实现使用

OutputDebugString
输出)。

enter image description here

如您所见,它并没有调用接口的所有方法。所以,我认为通过设置相同的参数应该很容易。 但是,是的,有些事情我没有考虑到。这种方法产生了

Null
解除引用异常,但没有成功。

注意:我知道第二个是“hacky”一个。

注意:实现默认动词处理程序的

CLSID
CLSID_ExecuteFolder

如何在新选项卡中打开文件夹?

windows winapi windows-shell shell32
1个回答
0
投票

第一个解决方案是使用官方的 UI Automation API

Microsoft UI Automation是一个辅助功能框架,可实现 用于提供和使用编程信息的 Windows 应用程序 关于用户界面 (UI)。它提供对大多数内容的编程访问 桌面上的 UI 元素。它使辅助技术产品、 例如屏幕阅读器,提供有关 UI 的信息以结束 用户并通过标准输入以外的方式操作 UI。用户界面 自动化还允许自动化测试脚本与 UI 交互。

此代码打开一个

c:\temp
文件夹(或选择已打开的文件夹)并向资源管理器窗口添加一个新选项卡:

int main()
{
  CoInitialize(nullptr);

  // get a pidl for a given folder
  LPITEMIDLIST pidl;
  if (SUCCEEDED(SHParseDisplayName(L"c:\\temp", nullptr, &pidl, 0, nullptr)))
  {
    // open the folder (or activate an already matching opened one)
    SHOpenFolderAndSelectItems(pidl, 1, (LPCITEMIDLIST*)&pidl, 0);

    // find window that displays this pidl
    IShellWindows* windows;
    if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&windows))))
    {
      // build buffer from pidl
      VARIANT url;
      VariantInit(&url);
      if (SUCCEEDED(InitVariantFromBuffer(pidl, ILGetSize(pidl), &url))) // propvarutil.h
      {
        long hwnd = 0;
        IDispatch* disp;
        VARIANT empty;
        VariantInit(&empty);
        windows->FindWindowSW(&url, &empty, SWC_BROWSER, &hwnd, 0, &disp);
        VariantClear(&url);
        VariantClear(&empty); // useless but we always pair calls

        if (disp) // should be null but we never know...
        {
          disp->Release();
        }

        if (hwnd)
        {
          //needs UIAutomationCore.h & UIAutomationClient.h
          IUIAutomation* automation;
          if (SUCCEEDED(CoCreateInstance(CLSID_CUIAutomation8, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&automation)))) // or CLSID_CUIAutomation
          {
            // get UIA element from handle
            IUIAutomationElement* window;
            automation->ElementFromHandle((UIA_HWND)(INT_PTR)hwnd, &window);
            if (window)
            {
              // window's class should be 'ShellTabWindowClass'
              // create inconditional "true" condition
              IUIAutomationCondition* trueCondition;
              automation->CreateTrueCondition(&trueCondition);
              if (trueCondition)
              {
                // create a tree walker to determine window's parent
                IUIAutomationTreeWalker* walker;
                automation->CreateTreeWalker(trueCondition, &walker);
                if (walker)
                {
                  IUIAutomationElement* parent;
                  walker->GetParentElement(window, &parent);
                  if (parent)
                  {
                    // create a condition to find the first button with AutomationId property set to AddButton
                    VARIANT v;
                    V_VT(&v) = VT_BSTR;
                    V_BSTR(&v) = SysAllocString(L"AddButton");
                    IUIAutomationCondition* condition;
                    automation->CreatePropertyCondition(UIA_AutomationIdPropertyId, v, &condition);
                    VariantClear(&v);
                    if (condition)
                    {
                      IUIAutomationElement* button;
                      // search button
                      parent->FindFirst(TreeScope_Subtree, condition, &button);
                      if (button)
                      {
                        // a button implements the "Invoke" pattern
                        IUnknown* unk;
                        button->GetCurrentPattern(UIA_InvokePatternId, &unk);
                        if (unk)
                        {
                          IUIAutomationInvokePattern* pattern;
                          unk->QueryInterface(&pattern);
                          if (pattern)
                          {
                            // press the button
                            pattern->Invoke();
                            pattern->Release();
                          }
                          unk->Release();
                        }
                        button->Release();
                      }
                      condition->Release();
                    }
                    parent->Release();
                  }
                  walker->Release();
                }
                trueCondition->Release();
              }
            }
            automation->Release();
          }
          CoUninitialize();
        }
      }
      windows->Release();
    }
    CoTaskMemFree(pidl);
  }
  CoUninitialize();
  return 0;
}

为了能够对 UIA 进行编程,您通常首先分析窗口的结构,例如使用 Windows SDK 中的 Inspect 工具 或较新的 Accessibility Insights 来了解您应该执行哪些操作。这是突出显示的资源管理器的“添加新选项卡”按钮的检查屏幕截图,它告诉我们该按钮的 Automation Id 属性设置为“AddButton”:

enter image description here

PS:有时,它不太明显,因为并不总是设置Automation Id

另一个解决方案是使用未记录的资源管理器的

WM_COMMAND
0xA21B (41499),它直接要求资源管理器打开一个新选项卡。因此,在前面的示例中,在按住
ShellTabWindowClass
窗口

后,您可以简单地用此替换所有 UIA 代码
...
if (hwnd)
{
  // window's class should be 'ShellTabWindowClass'
  // ask to open a new tab
  SendMessage((HWND)(INT_PTR)hwnd, WM_COMMAND, 0xA21B, 0);
}
...
© www.soinside.com 2019 - 2024. All rights reserved.