注意:此问题仅适用于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
输出)。
如您所见,它并没有调用接口的所有方法。所以,我认为通过设置相同的参数应该很容易。 但是,是的,有些事情我没有考虑到。这种方法产生了
Null
解除引用异常,但没有成功。
注意:我知道第二个是“hacky”一个。
注意:实现默认动词处理程序的
CLSID
是CLSID_ExecuteFolder
。
如何在新选项卡中打开文件夹?
第一个解决方案是使用官方的 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”:
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);
}
...