在拖拽文件还没有进入任何窗口时,怎样获取鼠标拖拽的文件信息? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
skylord
V2EX    Windows

在拖拽文件还没有进入任何窗口时,怎样获取鼠标拖拽的文件信息?

  •  2
     
  •   skylord 16 天前 2668 次点击

    像豆包电脑那样,鼠标在桌面上拖动一个文件,刚开始拖动,还没进入任何窗口,豆包就能感知到,然后在屏幕右下角弹出提示,可以帮忙解读文件。这个是怎么实现的? 目前,通过鼠标钩子判断是否在拖拽,如果在拖拽,就通过 OleGetClipboard 函数从剪贴板获取文件信息,但是剪贴板里面没有文件相关的信息。代码如下:

    #include <windows.h> #include <shlobj.h> #include <vector> #include <string> #include <iostream> #include <chrono> #include <thread> HHOOK g_mouseHook; bool g_isDragging = false; POINT g_dragStartPos = { 0, 0 }; const int DRAG_THRESHOLD = 3; // 拖动阈值(像素) void ExtractFileInfoFromDropClipboard(); void CheckOtherDataFormats(IDataObject* pDataObject); void ExtractFileTypeInfo(const std::wstring& filePath); LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { MSLLHOOKSTRUCT* pMouse = (MSLLHOOKSTRUCT*)lParam; if (wParam == WM_LBUTTONDOWN) { //std::cout << "Mouse Button Down at (" << pMouse->pt.x << ", " << pMouse->pt.y << ")\n"; // 你可以在这里检测是否是拖拽开始的条件 // 记录拖动起始位置 g_dragStartPos = pMouse->pt; g_isDragging = false; } if (wParam == WM_MOUSEMOVE) { // 检测鼠标是否有拖拽操作 if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) { // 检查是否超过拖动阈值 int deltaX = abs(pMouse->pt.x - g_dragStartPos.x); int deltaY = abs(pMouse->pt.y - g_dragStartPos.y); if (!g_isDragging && (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD)) { g_isDragging = true; std::cout << "move begin..." << std::endl; // 尝试从拖放剪贴板获取文件信息 ExtractFileInfoFromDropClipboard(); } } } if (wParam == WM_LBUTTONUP) { if (g_isDragging) { std::cout << "move end" << std::endl; g_isDragging = false; } } } return CallNextHookEx(g_mouseHook, nCode, wParam, lParam); } // 从拖放剪贴板提取文件信息 void ExtractFileInfoFromDropClipboard() { HRESULT hr = OleInitialize(nullptr); if (FAILED(hr)) { std::cerr << "OleInitialize failed: " << hr << std::endl; return; } IDataObject* pDataObject = nullptr; // 获取拖放剪贴板数据 hr = OleGetClipboard(&pDataObject); if (SUCCEEDED(hr) && pDataObject) { FORMATETC fmtetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; if (SUCCEEDED(pDataObject->QueryGetData(&fmtetc))) { STGMEDIUM stgmed; // 查询 HDROP 数据 hr = pDataObject->GetData(&fmtetc, &stgmed); if (SUCCEEDED(hr)) { HDROP hDrop = static_cast<HDROP>(GlobalLock(stgmed.hGlobal)); if (hDrop) { // 获取文件数量 UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0); std::cout << "drag file count: " << fileCount << std::endl; // 遍历所有文件 for (UINT i = 0; i < fileCount; i++) { // 获取文件路径长度 UINT pathLength = DragQueryFile(hDrop, i, nullptr, 0); if (pathLength > 0) { std::vector<TCHAR> buffer(pathLength + 1); DragQueryFile(hDrop, i, buffer.data(), pathLength + 1); std::wstring filePath(buffer.data()); std::wcout << L"file " << (i + 1) << L": " << filePath << std::endl; // 获取文件属性信息 ExtractFileTypeInfo(filePath); } } GlobalUnlock(stgmed.hGlobal); } ReleaseStgMedium(&stgmed); } } else { std::wcout << L"CF_HDROP format not supported" << std::endl; // 尝试其他数据格式 //CheckOtherDataFormats(pDataObject); } pDataObject->Release(); } else { std::wcerr << L"can not get clipboard data" << std::endl; } OleUninitialize(); } // 检查其他数据格式 void CheckOtherDataFormats(IDataObject* pDataObject) { // 查询支持的数据格式 IEnumFORMATETC* pEnumFormat = nullptr; HRESULT hr = pDataObject->EnumFormatEtc(DATADIR_GET, &pEnumFormat); if (SUCCEEDED(hr) && pEnumFormat) { FORMATETC fmtetc; ULONG fetched = 0; std::cout << "data format in clipboard:" << std::endl; while (pEnumFormat->Next(1, &fmtetc, &fetched) == S_OK && fetched == 1) { TCHAR formatName[256]; if (GetClipboardFormatName(fmtetc.cfFormat, formatName, 256) > 0) { std::wcout << L"format1: " << formatName << L" (ID: " << fmtetc.cfFormat << L")" << std::endl; } else { // 标准格式 std::string stdFormatName; switch (fmtetc.cfFormat) { case CF_TEXT: stdFormatName = "CF_TEXT"; break; case CF_UNICODETEXT: stdFormatName = "CF_UNICODETEXT"; break; case CF_BITMAP: stdFormatName = "CF_BITMAP"; break; case CF_DIB: stdFormatName = "CF_DIB"; break; case CF_HDROP: stdFormatName = "CF_HDROP"; break; case CF_LOCALE: stdFormatName = "CF_LOCALE"; break; case CF_OEMTEXT: stdFormatName = "CF_OEMTEXT"; break; default: stdFormatName = "unknown"; break; } std::cout << "format2: " << stdFormatName << " (ID: " << fmtetc.cfFormat << ")" << std::endl; } } pEnumFormat->Release(); } } // 提取文件类型信息 void ExtractFileTypeInfo(const std::wstring& filePath) { // 获取文件属性 DWORD attr = GetFileAttributes(filePath.c_str()); if (attr != INVALID_FILE_ATTRIBUTES) { if (attr & FILE_ATTRIBUTE_DIRECTORY) { std::wcout << L" type: directory" << std::endl; } else { std::wcout << L" type: file" << std::endl; // 获取文件扩展名 size_t dotPos = filePath.find_last_of(L'.'); if (dotPos != std::wstring::npos) { std::wstring extension = filePath.substr(dotPos); std::wcout << L" extension: " << extension << std::endl; } // 获取文件大小 HANDLE hFile = CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER fileSize; if (GetFileSizeEx(hFile, &fileSize)) { std::wcout << L" size: " << fileSize.QuadPart << L" bytes" << std::endl; } CloseHandle(hFile); } } // 显示文件属性 std::wcout << L" properties: "; if (attr & FILE_ATTRIBUTE_READONLY) std::wcout << L"[read-only]"; if (attr & FILE_ATTRIBUTE_HIDDEN) std::wcout << L"[hidden]"; if (attr & FILE_ATTRIBUTE_SYSTEM) std::wcout << L"[system]"; if (attr & FILE_ATTRIBUTE_ARCHIVE) std::wcout << L"[archive]"; std::wcout << std::endl; } } void InstallMouseHook() { g_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0); if (g_mouseHook == NULL) { std::cerr << "Failed to install mouse hook!" << std::endl; } } int main() { InstallMouseHook(); // 进入消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // 卸载钩子 UnhookWindowsHookEx(g_mouseHook); return 0; } 
    31 条回复    2025-11-30 10:00:06 +08:00
    BBrother
        1
    BBrother  
       16 天前
    创建一个全屏的透明窗口试试?
    huihushijie1996
        2
    huihushijie1996  
       16 天前
    桌面也是一个窗口
    huihushijie1996
        3
    huihushijie1996  
       16 天前
    @huihushijie1996 只需要截获桌面窗口的消息即可
    skylord
        4
    skylord  
    OP
       16 天前
    在 windows 资源管理器里拖动也能截获吗?
    skylord
        5
    skylord  
    OP
       16 天前
    @BBrother 窗口被遮挡住就不行了
    zjsxwc
        6
    zjsxwc  
       16 天前
    问下 ai ,就有代码:用 python 实现:捕获系统级的鼠标操作与文件拖拽
    若要获取非自身窗口的鼠标点击或文件拖拽(如系统全局范围),不能通过 DWM 实现,需借助鼠标钩子(如 SetWindowsHookEx 函数设置 WH_MOUSE_LL 钩子)捕获全局鼠标事件。而获取全局文件拖拽时,可结合 Shell 扩展编程或钩子监听资源管理器的文件选择操作,通过获取 SysListView32 控件(资源管理器文件列表控件)的句柄,进一步查询拖拽文件的相关信息,但这种方式需注意权限和系统兼容性问题。
    worldhandsomeboy
        7
    worldhandsomeboy  
       16 天前
    鼠标相关的 api 有一些属于焦点的函数,在你左击文件拖拽的时候,焦点所在的文件就是一个对象,包括路径、类型这些。
    skylord
        8
    skylord  
    OP
       16 天前
    @zjsxwc ai 提这些都试过了,注册 shell 扩展,鼠标钩子,dll 注入都有问题,没法实现
    sir283
        9
    sir283  
       16 天前
    你要用管理员权限运行你的软件啊,豆包都是用管理员权限 hook 的。
    skylord
        10
    skylord  
    OP
       16 天前   1
    @sir283 豆包没有管理员权限,试过很多次了
    guanzhangzhang
        11
    guanzhangzhang  
       16 天前
    qq 前不久更新后也有这种感知,然后我在设置里关闭了
    skylord
        12
    skylord  
    OP
       16 天前
    @guanzhangzhang 我们公司开发了个智能助手,也要这个功能
    momo1999
        13
    momo1999  
       16 天前
    拖拽应该是个单独的结构,不影响剪贴板吧,要不然我拖个文件,我之前复制的东西就没了?
    skylord
        14
    skylord  
    OP
       16 天前
    @momo1999 不是同一个剪贴板,有两个剪贴板,一个 ole 剪贴板(给文件拖拽用的),一个系统剪贴板(给复制粘贴用),拖拽不会影响系统剪贴板
    momo1999
        15
    momo1999  
       16 天前
    @skylord 看了一下资源管理器进程里面多了豆包的 dll ,路径在%temp%\doubao_ext\1.81.7\shellext.dll
    guanzhangzhang
        17
    guanzhangzhang  
       16 天前
    @skylord #12 看看有啥类似 processmonitor 啥的看看能不能监听到 qq 的这块大概 dll ,看看符号表啥的
    nnnnnnamgn
        18
    nnnnnnamgn  
       16 天前
    豆包那个实现应该很复杂,不如搞一个类似 360 加速器那样的小的悬浮窗,让用户自己拖拽文件进去
    skylord
        19
    skylord  
    OP
       15 天前
    @ljpCN 感谢,完整读了一遍,没发现什么线索
    skylord
        20
    skylord  
    OP
       15 天前
    @bluearc 不满足需求啊
    skylord
        21
    skylord  
    OP
       15 天前
    @momo1999 这个是个 ContextMenuHandlers,跟右键菜单相关的
    skylord
        22
    skylord  
    OP
       15 天前
    @worldhandsomeboy 能详细说说吗?
    worldhandsomeboy
        23
    worldhandsomeboy  
       15 天前
    创建了个 demo 监听粘贴板,在拖动桌面文件过程中很多时候没有写入剪贴板,有时候写入了但复现不出来。
    下载了 window 的豆包,没看到有这个功能。
    dode
        24
    dode  
       15 天前
    可能是监控鼠标位置
    skylord
        25
    skylord  
    OP
       15 天前
    @dode 通过鼠标钩子监控鼠标位置,但是拿不到文件信息
    skylord
        26
    skylord  
    OP
       15 天前
    @worldhandsomeboy 功能是有的,你在拖动 doc ,txt 等文档类的文件时,右下角会弹出一个 tips ,告诉你它可以帮你解读文件
    nilaoda
        27
    nilaoda  
       15 天前   4
    问了 Gemini 3 Pro Preview ,可以使用 UIAutomationClient 监听 SysDragImage 元素来实现。

    整个流程如下:
    1. 利用 UI Automation 监听 SysDragImage 出现以捕获拖拽行为
    2. 结合鼠标坐标定位与 Shell COM 接口预先读取并校验选中文件的类型
    3. 只有当文件类型符合要求时才弹出悬浮窗,且窗口的关闭逻辑完全依赖鼠标按键状态监测,实现精准触发

    .NET 调用比较方便,让他写了一个 WPF 的 demo ,不知道你要的是不是这种效果?



    https://gist.github.com/nilaoda/26b5f32bb383a3285cbe89434c30a232

    让 AI 用 C/C++实现一下就好。

    BTW ,可以研究研究 win11 顶部分享框的实现……
    ysc3839
        28
    ysc3839  
       15 天前 via Android
    不知道 SetWinEventHook 的 EVENT_OBJECT_DRAGSTART 等事件是否可行?
    https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook
    skylord
        29
    skylord  
    OP
       14 天前
    @nilaoda 感谢,我试试
    skylord
        30
    skylord  
    OP
       14 天前
    @ysc3839 感谢,我看一下这个文档
    skylord
        31
    skylord  
    OP
       14 天前
    @nilaoda 这个方式确实是可以的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2500 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 06:21 PVG 14:21 LAX 22:21 JFK 01:21
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86