You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows C++水印应用如何在所有虚拟桌面显示?

问题解决:Windows虚拟桌面下水印无法同步显示

核心原因

Windows 10的虚拟桌面机制中,窗口默认与创建它的虚拟桌面绑定,切换到新桌面后,未关联的窗口不会自动显示。仅监听WM_VIRTUAL_DESKTOP_CHANGED不足以解决问题:该消息仅在窗口自身所在虚拟桌面变化时触发,新建虚拟桌面时原有窗口不会收到此消息,且重建的窗口仍会绑定到原虚拟桌面。

解决方案步骤

  1. 使用Virtual Desktop API绑定窗口到当前虚拟桌面:通过Windows公开的IVirtualDesktopManager接口,在创建水印窗口后将其移动到当前虚拟桌面。
  2. 注册全局虚拟桌面变更通知:替代窗口消息的局部监听,通过IVirtualDesktopNotification捕获所有虚拟桌面的创建、切换事件,主动触发水印刷新。
  3. 优化窗口重建逻辑:确保销毁旧窗口后彻底处理消息队列,避免资源残留。

修改后的代码示例

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:50:04