在 rust 中如何使用 windows crate 获取文件管理当前 tab 中选中的文件路径
通过 IShellWindows 只能拿到选中的文件,但是没办法区分是哪个 tab 中选中的
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
}
}
上述代码只能获得所有选中的文件
你写的代码能遍历所有 IShellWindows 窗口,而且能获取选中的文件路径。不过还存在一个问题,它只能返回所有选中的文件,无法区分具体的Tab。在Windows文件管理器的多标签(Tab)是 Windows 11 开始新增的功能,而 IShellWindows 接口本身并没有原生支持区分这些标签。要准确获取哪个标签中选中了文件,需要通过特定的窗口句柄关联上标签页。这会涉及到结合 Windows API 处理多标签窗口。
我的解决方案是,EnumChildWindows + IShellView,可以通过 EnumChildWindows 获取当前文件管理器窗口的子窗口句柄,这样就能匹配到具体的 ShellView 实例。
Accesibility (UI Automation):利用 Windows 的 UI 自动化框架,获取特定 Tab 页中的内容。
具体可以通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView
代码:
use windows::{
core::*,
Win32::{
Foundation::HWND,
UI::WindowsAndMessaging::GetForegroundWindow,
UI::Accessibility::UIA_WindowControlTypeId,
UI::Automation::{
IUIAutomation, IUIAutomationElement, UIA_NamePropertyId,
},
}
};
fn get_active_tab_selected_file() -> Option<String> {
unsafe {
let hwnd = GetForegroundWindow();
if hwnd.0 == 0 {
println!("无法获取当前窗口句柄");
return None;
}
// 初始化 COM
CoInitializeEx(None, COINIT_DISABLE_OLE1DDE).ok()?;
// 创建 IShellWindows 实例
let shell_windows: IShellWindows = CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER).ok()?;
let count = shell_windows.Count().unwrap_or(0);
for i in 0..count {
let variant = VARIANT::from(i);
if let Ok(window) = shell_windows.Item(&variant) {
let web_browser: IWebBrowser2 = window.cast().ok()?;
// 检查窗口句柄是否匹配当前活动窗口
if web_browser.HWND().ok()?.0 as isize != hwnd.0 as isize {
continue;
}
let document = web_browser.Document().ok()?;
let folder_view: IShellFolderViewDual3 = document.cast().ok()?;
let selected_items = folder_view.SelectedItems().ok()?;
let selected_count = selected_items.Count().ok()?;
if selected_count > 0 {
let item = selected_items.Item(&VARIANT::from(0)).ok()?;
let path = item.Path().ok()?;
CoUninitialize();
return Some(path.to_string());
}
}
}
CoUninitialize();
None
}
}
在代码中并没有体现你在回复中所说的。
我的解决方案是,EnumChildWindows + IShellView,可以通过 EnumChildWindows 获取当前文件管理器窗口的子窗口句柄,这样就能匹配到具体的 ShellView 实例。
Accesibility (UI Automation):利用 Windows 的 UI 自动化框架,获取特定 Tab 页中的内容。
具体可以通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView
@_zhiqiu: 代码只是写了如何通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView,只是一个示例代码,你是对代码有疑问吗🤣,那我解释一下里面比较关键的点吧。
获取当前活动窗口句柄:使用 GetForegroundWindow() 获取当前活动的窗口。
遍历 IShellWindows:仍然用 IShellWindows 遍历所有文件管理器窗口。
匹配子窗口:根据窗口句柄 (HWND) 过滤非当前窗口的项目。
获取选中的文件路径:使用 IShellFolderViewDual3 获取选中的项目路径。
通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView。
@五号位: 代码只遍历了 IShellWindows,并没有 EnumChildWindows
(遍历文件管理器下的窗口) ,基本上都是示例里面的代码,
还有就是 GetForegroundWindow
方法只能拿到文件管理器这一层
由于我对于windows开发并不熟悉,所以需要比较详细一点的代码
@_zhiqiu: Windows 11 的文件管理器支持多标签,直接用 IShellWindows 是不能区分不同标签中的选中项的。所以通过EnumChildWindows遍历某个文件管理器窗口的子窗口,并找到特定的ShellView窗口(文件视图窗口)。区分具体标签页中的选中项,获取当前活动窗口(文件管理器)句柄,GetForegroundWindow 只能拿到文件管理器主窗口的句柄,使用 EnumChildWindows 遍历子窗口,找到文件视图窗口(ShellView),与 COM 结合,获取选中的文件路径,通过找到的 ShellView 视图窗口,再通过 IShellFolderViewDual3 获取选中的文件,这样应该清晰点了吧。
我再写一下相对详细完整一点的代码
use std::ptr::null_mut;
use windows::{
core::*,
Win32::{
Foundation::{HWND, LPARAM},
System::{
Com::{CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_LOCAL_SERVER, COINIT_MULTITHREADED},
Variant::VARIANT,
},
UI::WindowsAndMessaging::{EnumChildWindows, GetClassNameW, GetForegroundWindow},
},
Win32::UI::Shell::{IShellFolderViewDual3, IShellWindows, ShellWindows},
Win32::System::Ole::{IDispatch},
};
unsafe extern "system" fn enum_child_proc(hwnd: HWND, lparam: LPARAM) -> i32 {
let mut class_name = [0u16; 256];
// 获取窗口类名,判断是否是文件视图窗口
let length = GetClassNameW(hwnd, &mut class_name) as usize;
let class_name = String::from_utf16_lossy(&class_name[..length]);
if class_name == "SHELLDLL_DefView" {
// 找到了目标窗口,把句柄保存到 lparam 中传递回去
*(lparam.0 as *mut HWND) = hwnd;
// 停止枚举子窗口
return 0;
}
1 // 继续枚举其他子窗口
}
fn get_selected_file_from_active_tab() -> Option<String> {
unsafe {
// 初始化 COM 库
if CoInitializeEx(None, COINIT_MULTITHREADED).is_err() {
println!("初始化 COM 失败");
return None;
}
// 获取当前活动窗口句柄(文件管理器主窗口)
let foreground_hwnd = GetForegroundWindow();
if foreground_hwnd.0 == 0 {
println!("无法获取当前活动窗口句柄");
CoUninitialize();
return None;
}
// 遍历子窗口,查找 SHELLDLL_DefView(文件视图窗口)
let mut shell_view_hwnd = HWND(0);
EnumChildWindows(foreground_hwnd, Some(enum_child_proc), LPARAM(&mut shell_view_hwnd as *mut _ as isize));
if shell_view_hwnd.0 == 0 {
println!("未找到 SHELLDLL_DefView 窗口");
CoUninitialize();
return None;
}
// 创建 IShellWindows 实例
let shell_windows: IShellWindows = CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER).ok()?;
let count = shell_windows.Count().unwrap_or(0);
for i in 0..count {
let variant = VARIANT::from(i);
if let Ok(window) = shell_windows.Item(&variant) {
let web_browser: IDispatch = window.cast().ok()?;
// 检查子窗口句柄是否匹配
if let Ok(web_hwnd) = web_browser.cast::<windows::Win32::UI::Shell::IWebBrowser2>().and_then(|wb| wb.HWND()) {
if web_hwnd.0 != foreground_hwnd.0 {
continue;
}
}
// 获取选中的文件
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()?;
CoUninitialize();
return Some(path.to_string());
}
}
}
CoUninitialize();
None
}
}
fn main() {
match get_selected_file_from_active_tab() {
Some(path) => println!("选中的文件路径: {}", path),
None => println!("未找到选中的文件"),
}
}
代码中比较关键的点:
GetForegroundWindow:获取当前活动的文件管理器窗口句柄。
EnumChildWindows:遍历文件管理器窗口的子窗口,找到类名为 "SHELLDLL_DefView" 的子窗口,即文件视图窗口
CoCreateInstance:创建 IShellWindows 实例,用于遍历所有文件管理器窗口。
匹配子窗口句柄:使用 IWebBrowser2 获取文件视图窗口句柄,确保与当前活动的窗口一致。
获取选中文件路径:通过 IShellFolderViewDual3 获取选中的文件路径。
一些需要注意的点:
COM 初始化:必须在使用 COM 对象前调用 CoInitializeEx。
句柄匹配:确保通过 EnumChildWindows 找到的子窗口句柄和文件管理器窗口匹配。
多标签支持:这段代码通过窗口句柄关联标签页,确保只获取当前标签页中的选中项。
代码可以准确获取当前活动窗口(文件管理器)中的选中文件路径。还处理了多标签的情况,通过子窗口句柄进行精确匹配。
@五号位: 非常感谢,代码可以运行
@_zhiqiu: 不客气😉
@五号位: 是我看错了,代码还是只拿到了第一个tab中选中的