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

2025 年 11 月 28 日
 skylord

像豆包电脑那样,鼠标在桌面上拖动一个文件,刚开始拖动,还没进入任何窗口,豆包就能感知到,然后在屏幕右下角弹出提示,可以帮忙解读文件。这个是怎么实现的? 目前,通过鼠标钩子判断是否在拖拽,如果在拖拽,就通过 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;
}
4250 次点击
所在节点    Windows
31 条回复
skylord
2025 年 11 月 28 日
@momo1999 这个是个 ContextMenuHandlers,跟右键菜单相关的
skylord
2025 年 11 月 28 日
@worldhandsomeboy 能详细说说吗?
worldhandsomeboy
2025 年 11 月 28 日
创建了个 demo 监听粘贴板,在拖动桌面文件过程中很多时候没有写入剪贴板,有时候写入了但复现不出来。
下载了 window 的豆包,没看到有这个功能。
dode
2025 年 11 月 28 日
可能是监控鼠标位置
skylord
2025 年 11 月 28 日
@dode 通过鼠标钩子监控鼠标位置,但是拿不到文件信息
skylord
2025 年 11 月 28 日
@worldhandsomeboy 功能是有的,你在拖动 doc ,txt 等文档类的文件时,右下角会弹出一个 tips ,告诉你它可以帮你解读文件
nilaoda
2025 年 11 月 28 日
问了 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
2025 年 11 月 29 日
不知道 SetWinEventHook 的 EVENT_OBJECT_DRAGSTART 等事件是否可行?
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook
skylord
2025 年 11 月 29 日
@nilaoda 感谢,我试试
skylord
2025 年 11 月 29 日
@ysc3839 感谢,我看一下这个文档
skylord
2025 年 11 月 30 日
@nilaoda 这个方式确实是可以的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/1175553

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX