如何在其他进程的窗口句柄(HWND)上绘制BITMAP
问题分析与解决方案
你的代码存在几个核心问题,导致绘制的内容无法持久显示在计算器窗口上:
- 临时绘制被窗口重绘覆盖:直接通过
GetDC获取窗口DC绘制的内容是临时的,计算器窗口会在收到WM_PAINT消息时自动重绘自身界面,完全覆盖你绘制的截图。 - 未适配UWP计算器的窗口结构:现代Windows计算器是UWP应用,
ApplicationFrameWindow只是外层容器,实际的内容显示窗口是它的子窗口,直接绘制到外层容器无法覆盖内部的计算器控件。 - 无持续更新机制:代码只执行一次绘制,无法实现"持续显示最新截图"的需求。
解决步骤
1. 定位计算器的实际内容窗口
遍历ApplicationFrameWindow的子窗口,找到负责显示内容的核心窗口(类名通常为Windows.UI.Core.CoreWindow):
HWND FindCalculatorContentWnd(HWND hFrameWnd) { return FindWindowEx(hFrameWnd, NULL, L"Windows.UI.Core.CoreWindow", NULL); }
2. 子类化计算器内容窗口,拦截重绘消息
通过SetWindowSubclass子类化窗口,在WM_PAINT触发时强制绘制桌面截图,覆盖原界面:
#include <windows.h> #include <commctrl.h> #pragma comment(lib, "comctl32.lib") HBITMAP g_hDesktopBmp = NULL; // 存储最新桌面截图 int g_ScreenWidth, g_ScreenHeight; LRESULT CALLBACK CalculatorSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { switch (uMsg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); if (g_hDesktopBmp) { RECT rc; GetClientRect(hWnd, &rc); int clientWidth = rc.right - rc.left; int clientHeight = rc.bottom - rc.top; // 创建兼容DC并绘制截图 HDC hMemDC = CreateCompatibleDC(hdc); HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, g_hDesktopBmp); StretchBlt(hdc, 0, 0, clientWidth, clientHeight, hMemDC, 0, 0, g_ScreenWidth, g_ScreenHeight, SRCCOPY); SelectObject(hMemDC, hOldBmp); DeleteDC(hMemDC); } EndPaint(hWnd, &ps); return 0; // 跳过原WM_PAINT处理,不绘制计算器默认界面 } case WM_ERASEBKGND: return 1; // 禁止擦除背景,避免闪烁 case WM_DESTROY: RemoveWindowSubclass(hWnd, CalculatorSubclassProc, uIdSubclass); if (g_hDesktopBmp) DeleteObject(g_hDesktopBmp); break; } return DefSubclassProc(hWnd, uMsg, wParam, lParam); }
3. 定时更新截图并触发重绘
用SetTimer创建定时器,每隔一段时间更新桌面截图,并调用InvalidateRect触发计算器窗口重绘:
VOID UpdateDesktopScreenshot() { HDC hScreenDC = GetDC(NULL); if (!hScreenDC) return; // 释放旧位图 if (g_hDesktopBmp) { DeleteObject(g_hDesktopBmp); g_hDesktopBmp = NULL; } // 捕获桌面截图 g_ScreenWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN); g_ScreenHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN); g_hDesktopBmp = CreateCompatibleBitmap(hScreenDC, g_ScreenWidth, g_ScreenHeight); HDC hMemDC = CreateCompatibleDC(hScreenDC); HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, g_hDesktopBmp); BitBlt(hMemDC, 0, 0, g_ScreenWidth, g_ScreenHeight, hScreenDC, 0, 0, SRCCOPY); SelectObject(hMemDC, hOldBmp); DeleteDC(hMemDC); ReleaseDC(NULL, hScreenDC); } VOID CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { UpdateDesktopScreenshot(); InvalidateRect(hWnd, NULL, FALSE); // 触发窗口重绘 }
4. 主流程整合
int main() { // 查找计算器外层窗口 HWND hCalcFrame = FindWindowA("ApplicationFrameWindow", "Calculator"); if (!hCalcFrame) return 1; // 查找实际内容窗口 HWND hCalcContent = FindCalculatorContentWnd(hCalcFrame); if (!hCalcContent) return 1; // 子类化内容窗口 if (!SetWindowSubclass(hCalcContent, CalculatorSubclassProc, 0, 0)) return 1; // 初始化截图+设置定时器(每100ms更新一次) UpdateDesktopScreenshot(); SetTimer(hCalcContent, 1, 100, TimerProc); // 消息循环维持程序运行 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } KillTimer(hCalcContent, 1); return 0; }
关键说明
- 子类化窗口:通过拦截
WM_PAINT完全接管绘制逻辑,确保每次重绘都显示最新截图。 - UWP窗口适配:必须定位到内部
CoreWindow才能覆盖计算器的实际界面,直接绘制外层容器无效。 - 资源管理:注意及时释放GDI资源(位图、DC等),避免内存泄漏。
- 定时器更新:定时捕获桌面并触发重绘,实现"持续显示最新截图"的需求。
内容的提问来源于stack exchange,提问作者curiousgeorge8954
相关产品推荐
相关产品推荐

