You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何监听Windows 10中其他窗口在虚拟桌面间的移动事件?

如何监听Windows 10虚拟桌面间的窗口移动事件

确实,Windows的SetWinEventHook提供的标准事件里,没有专门对应窗口在虚拟桌面间移动的条目。不过我们可以通过结合公开的虚拟桌面COM接口+WinEvent钩子,或者使用系统内部的虚拟桌面通知接口来实现这个需求,下面给你两种可行的方案:

方案一:基于公开API的稳定实现(推荐)

Windows 10 1607及以上版本提供了公开的IVirtualDesktopManager接口,我们可以用它获取窗口所属的虚拟桌面ID,再配合EVENT_OBJECT_PROPERTYCHANGE事件监听窗口属性变化,从而判断窗口是否跨桌面移动。

具体步骤

  1. 初始化COM环境:因为要调用虚拟桌面的COM接口,必须先初始化COM。
  2. 创建IVirtualDesktopManager实例:用来获取任意窗口的所属桌面ID。
  3. 注册WinEvent钩子:监听EVENT_OBJECT_PROPERTYCHANGE事件,针对所有窗口对象(OBJID_WINDOW)。
  4. 在回调中对比桌面ID:每次窗口属性变化时,获取当前窗口的桌面ID,和之前记录的ID对比,不一致则说明窗口跨桌面移动了。

代码示例(C++)

#include <windows.h>
#include <shobjidl.h>
#include <unordered_map>
#include <mutex>

IVirtualDesktopManager* g_pVDM = nullptr;
std::unordered_map<HWND, GUID> g_windowDesktopMap;
std::mutex g_mapMutex;

// WinEvent回调函数
void CALLBACK WinEventProc(HWINEVENTHOOK hook, DWORD event, HWND hwnd, 
                           LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
{
    // 只处理窗口的属性变化事件
    if (event != EVENT_OBJECT_PROPERTYCHANGE || idObject != OBJID_WINDOW || hwnd == nullptr)
        return;

    GUID currentDesktopId;
    HRESULT hr = g_pVDM->GetWindowDesktopId(hwnd, &currentDesktopId);
    if (FAILED(hr))
        return;

    std::lock_guard<std::mutex> lock(g_mapMutex);
    auto it = g_windowDesktopMap.find(hwnd);
    if (it != g_windowDesktopMap.end())
    {
        // 对比前后桌面ID,不一致则触发移动逻辑
        if (memcmp(&it->second, &currentDesktopId, sizeof(GUID)) != 0)
        {
            OutputDebugStringA("窗口已移动到其他虚拟桌面!");
            g_windowDesktopMap[hwnd] = currentDesktopId;
        }
    }
    else
    {
        // 首次记录窗口的桌面ID
        g_windowDesktopMap[hwnd] = currentDesktopId;
    }
}

int main()
{
    // 初始化COM
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);

    // 创建IVirtualDesktopManager实例
    HRESULT hr = CoCreateInstance(CLSID_VirtualDesktopManager, NULL, 
                                  CLSCTX_ALL, IID_IVirtualDesktopManager, (void**)&g_pVDM);
    if (SUCCEEDED(hr))
    {
        // 注册WinEvent钩子
        HWINEVENTHOOK hook = SetWinEventHook(EVENT_OBJECT_PROPERTYCHANGE, EVENT_OBJECT_PROPERTYCHANGE, 
                                             NULL, WinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
        
        // 消息循环保持程序运行
        MSG msg;
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // 清理资源
        UnhookWinEvent(hook);
        g_pVDM->Release();
    }

    CoUninitialize();
    return 0;
}

注意事项

  • 需要链接ole32.libuser32.lib编译。
  • 可以额外监听EVENT_OBJECT_DESTROY事件,在窗口销毁时从g_windowDesktopMap中移除条目,避免内存泄漏。
  • 容器g_windowDesktopMap加了互斥锁,保证多线程下的安全性(WinEvent回调可能在任意线程触发)。

方案二:基于内部API的直接通知(兼容性需注意)

Windows Shell提供了未公开的IVirtualDesktopManagerInternal接口,它支持注册IVirtualDesktopNotification回调,其中的OnWindowMove方法会在窗口跨桌面移动时直接触发,无需自己对比ID。但因为是内部接口,可能随Windows版本更新发生变化,适合对兼容性要求不高的场景。

核心要点

  1. 定义内部接口的GUID(系统头文件未提供):
const CLSID CLSID_VirtualDesktopManagerInternal = { 0xC5E0CDCA, 0x7B6E, 0x41B2, { 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B } };
const IID IID_IVirtualDesktopManagerInternal = { 0xAF8DA486, 0x95BB, 0x4460, { 0xB3, 0xB7, 0xE6, 0x77, 0x94, 0x01, 0x6E, 0x76 } };
const IID IID_IVirtualDesktopNotification = { 0xA501FDEC, 0x4A09, 0x464C, { 0xAE, 0x4E, 0x1B, 0x9C, 0x21, 0xB8, 0x49, 0x18 } };
  1. 实现IVirtualDesktopNotification接口,重写OnWindowMove方法处理窗口移动事件。
  2. 通过IVirtualDesktopManagerInternal::RegisterForVirtualDesktopNotifications注册回调。

内容的提问来源于stack exchange,提问作者LOST

火山引擎 最新活动