如何在Rust中获取Win11资源管理器当前标签选中文件的路径
Win11多标签资源管理器获取当前标签页选中文件完整路径
需求
- 在Win11多标签资源管理器中选中单个文件
- 按下空格键后,自研程序获取该文件的完整路径
当前进度
已实现获取所有资源管理器窗口中选中文件的路径,但无法定位到当前活动标签页的选中文件
已尝试方案
1. 通过IShellWindows
仅能获取资源管理器主窗口的选中文件,无法区分多标签页,相关Rust代码:
fn get_select_file_path(hwnd: HWND) -> Option<String> { // hwnd 是通过 WindowsAndMessaging::GetForegroundWindow(); 获取的当前活动窗口句柄 unsafe { // 初始化 COM 库 let com = CoInitializeEx(None, COINIT_DISABLE_OLE1DDE); if com.is_err() { return None; } let hr: Result<IShellWindows, windows::core::Error> = CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER); // let hr = CoCreateInstance(&ShellWindows, None, CLSCTX_INPROC_HANDLER); if hr.is_err() { println!("创建 IShellWindows 失败"); CoUninitialize(); // 清理 COM return None; // 创建 IShellWindows 失败 } let shell_windows = hr.unwrap(); let mut target_path = None; let count = shell_windows.Count().unwrap_or_default(); for i in 0..count { let variant = VARIANT::from(i); let window: IDispatch = shell_windows.Item(&variant).ok()?; let web_browser: IWebBrowser2 = window.cast().ok()?; // 检查窗口是否与当前活动窗口匹配 let item_hwnd = web_browser.HWND().ok()?; if item_hwnd.0 != hwnd.0 as isize { continue; } let a = web_browser.LocationURL().ok()?; println!("web_browser Path: {:?}", a); // 通过IWebBrowser2获取文件夹视图并获取选中的项目 let document = web_browser.Document().ok()?; let folder_view: IShellFolderViewDual3 = document.cast().ok()?; let selected_items = folder_view.SelectedItems().ok()?; let count = selected_items.Count().ok()?; if count > 0 { let item = selected_items.Item(&VARIANT::from(0)).ok()?; let path = item.Path().ok()?; target_path = Some(path.to_string()); break } } // 清理 COM CoUninitialize(); target_path } }
2. 通过IUIAutomation
能获取当前标签页选中文件的名称,但无法直接获取完整路径,相关Rust代码:
unsafe fn find_tab_items(ui_automation: &IUIAutomation, element: &IUIAutomationElement, file_name: &mut String) -> Result<(), WError> { // 查找子元素 let condition = ui_automation.CreatePropertyCondition(UIA_ControlTypePropertyId, &VARIANT::from(UIA_ListItemControlTypeId.0))?; let list_element = element.FindAll(TreeScope_Descendants, &condition)?; let count = list_element.Length()?; for i in 0..count { let item = list_element.GetElement(i)?; // 尝试获取 SelectionItemPattern let selection_pattern: IUIAutomationSelectionItemPattern = item.GetCurrentPatternAs(UIA_SelectionItemPatternId)?; // 检查是否选中 let is_selected = selection_pattern.CurrentIsSelected()?; if is_selected.as_bool().eq(&true) { // 获取文件名或路径 let tmp_name = item.GetCurrentPropertyValue(UIA_NamePropertyId)?; *file_name = tmp_name.to_string(); break } } Ok(()) }
解决方案
方案一:UIA拼接路径(快速实现)
利用UIA分别获取当前标签页的文件夹路径和选中文件名,拼接得到完整路径:
- 获取当前标签页的文件夹路径
unsafe fn get_current_folder_path(ui_automation: &IUIAutomation, window_element: &IUIAutomationElement) -> Result<String, WError> { // 定位地址栏输入框(Edit控件) let edit_condition = ui_automation.CreatePropertyCondition(UIA_ControlTypePropertyId, &VARIANT::from(UIA_EditControlTypeId.0))?; let address_bar = window_element.FindFirst(TreeScope_Descendants, &edit_condition)?; // 读取地址栏的文本值 let folder_path_variant = address_bar.GetCurrentPropertyValue(UIA_ValueValuePropertyId)?; Ok(folder_path_variant.to_string()) }
- 拼接完整路径
// 假设已通过find_tab_items获取选中文件名file_name let folder_path = get_current_folder_path(&ui_automation, &window_element)?; let full_path = format!("{}\\{}", folder_path, file_name);
方案二:通过Shell COM接口精准获取(稳定方案)
Win11多标签资源管理器的每个标签页对应独立的IShellView,通过窗口句柄获取当前活动标签的Shell接口,直接读取选中文件的完整路径:
use windows::Win32::UI::Shell::{IShellBrowser, IShellView, SHGetInstanceExplorer, SVSI_SELECTION}; use windows::Win32::System::Com::IDataObject; use windows::Win32::UI::Shell::SHGetPathFromIDListW; use windows::core::PCWSTR; unsafe fn get_current_tab_selected_path(hwnd: HWND) -> Option<String> { // 获取当前窗口的IShellBrowser let mut shell_browser: Option<IShellBrowser> = None; if SHGetInstanceExplorer(hwnd, &mut shell_browser).is_err() || shell_browser.is_none() { return None; } let shell_browser = shell_browser.unwrap(); // 获取当前活动标签的IShellView let mut shell_view: Option<IShellView> = None; if shell_browser.QueryActiveShellView(&mut shell_view).is_err() || shell_view.is_none() { return None; } let shell_view = shell_view.unwrap(); // 获取选中项的IDataObject let mut data_obj: Option<IDataObject> = None; if shell_view.GetSelectedItems(SVSI_SELECTION, &mut data_obj).is_err() || data_obj.is_none() { return None; } let data_obj = data_obj.unwrap(); // 从IDataObject中提取PIDL并转换为本地路径 // 此处省略IDataObject解析PIDL的代码,可通过STGMEDIUM和FORMATETC获取PIDL // 示例:假设已获取到PIDL指针pidl let mut path_buf = [0u16; 260]; if SHGetPathFromIDListW(pidl, &mut path_buf).as_bool() { let full_path = String::from_utf16_lossy(&path_buf).trim_end_matches('\0').to_string(); Some(full_path) } else { None } }
注意事项
- 调用COM接口时必须正确初始化(
CoInitializeEx)和释放(CoUninitialize),避免内存泄漏。 - Win11资源管理器的控件结构可能随系统更新变化,UIA方案需适配不同版本的控件属性。
- 使用
SHGetInstanceExplorer时,确保传入的是资源管理器窗口的顶层句柄。
内容的提问来源于stack exchange,提问作者郭纪坤
相关产品推荐
相关产品推荐

