Windows C++水印应用如何在所有虚拟桌面显示?
问题解决:Windows虚拟桌面下水印无法同步显示
核心原因
Windows 10的虚拟桌面机制中,窗口默认与创建它的虚拟桌面绑定,切换到新桌面后,未关联的窗口不会自动显示。仅监听WM_VIRTUAL_DESKTOP_CHANGED不足以解决问题:该消息仅在窗口自身所在虚拟桌面变化时触发,新建虚拟桌面时原有窗口不会收到此消息,且重建的窗口仍会绑定到原虚拟桌面。
解决方案步骤
- 使用Virtual Desktop API绑定窗口到当前虚拟桌面:通过Windows公开的
IVirtualDesktopManager接口,在创建水印窗口后将其移动到当前虚拟桌面。 - 注册全局虚拟桌面变更通知:替代窗口消息的局部监听,通过
IVirtualDesktopNotification捕获所有虚拟桌面的创建、切换事件,主动触发水印刷新。 - 优化窗口重建逻辑:确保销毁旧窗口后彻底处理消息队列,避免资源残留。
修改后的代码示例
1. 添加COM环境初始化与接口依赖
#include <ShObjIdl.h> #include <atlbase.h> // 程序启动时初始化COM环境 void InitCOM() { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); } // 程序退出时释放COM资源 void UninitCOM() { CoUninitialize(); }
2. 修改CreateWatermarkWindow函数
创建窗口后,强制将其关联到当前虚拟桌面:
HWND CreateWatermarkWindow(HINSTANCE hInstance, RECT screenRect) { HWND hWnd = CreateWindowExW( WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TOPMOST | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT, szWindowClass, szTitle, WS_POPUP, screenRect.left, screenRect.top, screenRect.right - screenRect.left, screenRect.bottom - screenRect.top, nullptr, nullptr, hInstance, nullptr ); BYTE alpha = static_cast<BYTE>(OpacityLevel * 255); SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), alpha, LWA_COLORKEY | LWA_ALPHA); // 将窗口移动到当前虚拟桌面 CComPtr<IVirtualDesktopManager> pVDManager; if (SUCCEEDED(pVDManager.CoCreateInstance(CLSID_VirtualDesktopManager))) { BOOL isOnCurrent = FALSE; if (SUCCEEDED(pVDManager->IsWindowOnCurrentVirtualDesktop(hWnd, &isOnCurrent)) && !isOnCurrent) { pVDManager->MoveWindowToCurrentVirtualDesktop(hWnd); } } ShowWindow(hWnd, SW_SHOW); UpdateWindow(hWnd); windows.push_back(hWnd); return hWnd; }
3. 实现全局虚拟桌面通知回调
// 全局虚拟桌面通知处理类 class CVirtualDesktopNotification : public IVirtualDesktopNotification { private: LONG m_cRef; HINSTANCE m_hInstance; public: CVirtualDesktopNotification(HINSTANCE hInst) : m_cRef(1), m_hInstance(hInst) {} // IUnknown接口实现 STDMETHODIMP QueryInterface(REFIID riid, void** ppv) { if (riid == IID_IUnknown || riid == IID_IVirtualDesktopNotification) { *ppv = static_cast<IVirtualDesktopNotification*>(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); } STDMETHODIMP_(ULONG) Release() { ULONG cRef = InterlockedDecrement(&m_cRef); if (cRef == 0) delete this; return cRef; } // 虚拟桌面创建时触发刷新 STDMETHODIMP VirtualDesktopCreated(IVirtualDesktop* /*pDesktop*/) { RefreshWatermarkWindows(m_hInstance); return S_OK; } // 虚拟桌面切换时触发刷新 STDMETHODIMP VirtualDesktopSwitched(IVirtualDesktop* /*pDesktopOld*/, IVirtualDesktop* /*pDesktopNew*/) { RefreshWatermarkWindows(m_hInstance); return S_OK; } // 其他未用到的接口方法默认返回S_OK STDMETHODIMP VirtualDesktopDestroyBegin(IVirtualDesktop*, IVirtualDesktop*) { return S_OK; } STDMETHODIMP VirtualDesktopDestroyFailed(IVirtualDesktop*, IVirtualDesktop*) { return S_OK; } STDMETHODIMP VirtualDesktopDestroyed(IVirtualDesktop*, IVirtualDesktop*) { return S_OK; } STDMETHODIMP VirtualDesktopMoved(IVirtualDesktop*, UINT, UINT) { return S_OK; } }; // 全局通知对象 CComPtr<IVirtualDesktopNotificationManager> g_pNotificationManager; CComPtr<IVirtualDesktopNotification> g_pNotification; // 注册全局虚拟桌面通知 void RegisterVirtualDesktopNotification(HINSTANCE hInst) { CComPtr<IVirtualDesktopManagerInternal> pVDManagerInternal; if (SUCCEEDED(pVDManagerInternal.CoCreateInstance(CLSID_VirtualDesktopManagerInternal))) { if (SUCCEEDED(pVDManagerInternal->GetNotificationManager(&g_pNotificationManager))) { g_pNotification = new CVirtualDesktopNotification(hInst); g_pNotificationManager->Register(g_pNotification); } } } // 取消注册全局通知 void UnregisterVirtualDesktopNotification() { if (g_pNotificationManager && g_pNotification) { g_pNotificationManager->Unregister(g_pNotification); g_pNotification.Release(); g_pNotificationManager.Release(); } }
4. 调整WndProc与WinMain逻辑
移除原WM_VIRTUAL_DESKTOP_CHANGED处理,替换为全局通知:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: { // 原绘制逻辑保持不变 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); HDC memDC = CreateCompatibleDC(hdc); if (!memDC) { DebugOutput(L"Failed to create memory DC\n"); EndPaint(hWnd, &ps); return 0; } HBITMAP memBitmap = CreateCompatibleBitmap(hdc, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top); if (!memBitmap) { DebugOutput(L"Failed to create compatible bitmap\n"); DeleteDC(memDC); EndPaint(hWnd, &ps); return 0; } HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap); Graphics graphics(memDC); graphics.Clear(BackgroundColor); std::wstring watermarkText = EnsureTextLength(GetWatermarkText()); DrawWatermarks(graphics, watermarkText, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top); BitBlt(hdc, ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, memDC, 0, 0, SRCCOPY); SelectObject(memDC, oldBitmap); DeleteObject(memBitmap); DeleteDC(memDC); EndPaint(hWnd, &ps); } break; case WM_DESTROY: UnregisterVirtualDesktopNotification(); UninitCOM(); PostQuitMessage(0); break; case WM_DISPLAYCHANGE: RefreshWatermarkWindows(hInst); break; case WM_ACTIVATEAPP: case WM_NEXTDLGCTL: case WM_SHOWWINDOW: SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { InitCOM(); RegisterVirtualDesktopNotification(hInstance); // 原窗口注册、初始化逻辑保持不变 MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }
关键说明
IVirtualDesktopManager是Windows公开接口,无需特殊权限即可使用,确保窗口在当前虚拟桌面显示。- 全局通知
IVirtualDesktopNotification能捕获所有虚拟桌面的创建和切换事件,保证水印及时同步到新桌面。 - 销毁窗口后处理消息队列的逻辑保留,避免旧窗口资源残留导致的异常。
内容的提问来源于stack exchange,提问作者Ghaith Saaa
相关产品推荐
相关产品推荐

