首页 新闻 会员 周边 捐助

在 rust 中如何获取windows文件管理当前 tab 中选中的文件路径?

0
悬赏园豆:200 [已解决问题] 解决于 2024-10-14 17:39

在 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
        }
    }

上述代码只能获得所有选中的文件

_zhiqiu的主页 _zhiqiu | 菜鸟二级 | 园豆:207
提问于:2024-10-14 11:48
< >
分享
最佳答案
1

你写的代码能遍历所有 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
    }
}

收获园豆:200
五号位 | 菜鸟二级 |园豆:344 | 2024-10-14 15:25

在代码中并没有体现你在回复中所说的。

我的解决方案是,EnumChildWindows + IShellView,可以通过 EnumChildWindows 获取当前文件管理器窗口的子窗口句柄,这样就能匹配到具体的 ShellView 实例。
Accesibility (UI Automation):利用 Windows 的 UI 自动化框架,获取特定 Tab 页中的内容。
具体可以通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView

_zhiqiu | 园豆:207 (菜鸟二级) | 2024-10-14 15:39

@_zhiqiu: 代码只是写了如何通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView,只是一个示例代码,你是对代码有疑问吗🤣,那我解释一下里面比较关键的点吧。

获取当前活动窗口句柄:使用 GetForegroundWindow() 获取当前活动的窗口。
遍历 IShellWindows:仍然用 IShellWindows 遍历所有文件管理器窗口。
匹配子窗口:根据窗口句柄 (HWND) 过滤非当前窗口的项目。
获取选中的文件路径:使用 IShellFolderViewDual3 获取选中的项目路径。

通过 EnumChildWindows 获取特定 HWND 下的所有子窗口,然后判断某个窗口是否是对应的 Tab 或 ShellView。

五号位 | 园豆:344 (菜鸟二级) | 2024-10-14 16:10

@五号位: 代码只遍历了 IShellWindows,并没有 EnumChildWindows (遍历文件管理器下的窗口) ,基本上都是示例里面的代码,
还有就是 GetForegroundWindow 方法只能拿到文件管理器这一层

由于我对于windows开发并不熟悉,所以需要比较详细一点的代码

_zhiqiu | 园豆:207 (菜鸟二级) | 2024-10-14 16:30

@_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 找到的子窗口句柄和文件管理器窗口匹配。
多标签支持:这段代码通过窗口句柄关联标签页,确保只获取当前标签页中的选中项。

代码可以准确获取当前活动窗口(文件管理器)中的选中文件路径。还处理了多标签的情况,通过子窗口句柄进行精确匹配。

五号位 | 园豆:344 (菜鸟二级) | 2024-10-14 16:48

@五号位: 非常感谢,代码可以运行

_zhiqiu | 园豆:207 (菜鸟二级) | 2024-10-14 17:38

@_zhiqiu: 不客气😉

五号位 | 园豆:344 (菜鸟二级) | 2024-10-14 17:45

@五号位: 是我看错了,代码还是只拿到了第一个tab中选中的

_zhiqiu | 园豆:207 (菜鸟二级) | 2024-10-14 17:51
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册