如何在原生C++中获取超出窗体客户区的ListBox完整位图?
在原生C++中捕获超出窗体的ListBox完整位图
核心思路
ListBox默认仅绘制可见区域,要获取全部1000项的完整位图,需通过计算控件总高度、创建足够大的内存绘图上下文,再逐段滚动ListBox并绘制每部分内容到内存位图中,最终拼接出完整图像。
实现步骤与代码示例
1. 计算ListBox的完整尺寸
首先获取ListBox的项数、每项高度,进而计算总高度;同时获取控件的客户区宽度:
HWND hListBox = /* 你的ListBox句柄 */; // 获取项数与单一项高度 int itemCount = SendMessage(hListBox, LB_GETCOUNT, 0, 0); int itemHeight = SendMessage(hListBox, LB_GETITEMHEIGHT, 0, 0); int totalHeight = itemCount * itemHeight; // 获取ListBox客户区宽度 RECT rcListClient; GetClientRect(hListBox, &rcListClient); int listWidth = rcListClient.right - rcListClient.left;
2. 创建内存DC与位图
创建与屏幕兼容的内存绘图上下文和足够大的位图,用于存储完整内容:
// 获取屏幕DC作为模板 HDC hScreenDC = GetDC(NULL); // 创建兼容内存DC HDC hMemDC = CreateCompatibleDC(hScreenDC); // 创建对应尺寸的兼容位图 HBITMAP hMemBitmap = CreateCompatibleBitmap(hScreenDC, listWidth, totalHeight); // 将位图选入内存DC,保存原有位图用于后续恢复 HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hMemBitmap); // 填充位图背景(使用系统窗口背景色) COLORREF bgColor = GetSysColor(COLOR_WINDOW); HBRUSH bgBrush = CreateSolidBrush(bgColor); FillRect(hMemDC, &rcListClient, bgBrush); DeleteObject(bgBrush);
3. 逐段滚动并绘制ListBox内容
循环设置ListBox的顶部项,让控件依次显示不同段的内容,再通过WM_PRINT消息让控件将自身绘制到内存DC的对应位置:
// 保存ListBox原有的顶部项索引,绘制完成后恢复 int originalTopIndex = SendMessage(hListBox, LB_GETTOPINDEX, 0, 0); // 计算单次可绘制的可见项数 int visibleItemCount = rcListClient.bottom / itemHeight; for (int startIndex = 0; startIndex < itemCount; startIndex += visibleItemCount) { // 设置当前要显示的顶部项 SendMessage(hListBox, LB_SETTOPINDEX, startIndex, 0); // 强制更新控件确保内容刷新 UpdateWindow(hListBox); // 计算当前段在内存位图中的绘制位置 int destTop = startIndex * itemHeight; int destBottom = min((startIndex + visibleItemCount) * itemHeight, totalHeight); // 偏移内存DC的原点,让控件绘制到对应位置 SetWindowOrgEx(hMemDC, 0, -destTop, NULL); // 发送WM_PRINT让ListBox绘制自身到内存DC SendMessage(hListBox, WM_PRINT, (WPARAM)hMemDC, PRF_CLIENT | PRF_CHILDREN | PRF_ERASEBKGND); // 恢复DC原点 SetWindowOrgEx(hMemDC, 0, 0, NULL); } // 恢复ListBox原有的顶部项 SendMessage(hListBox, LB_SETTOPINDEX, originalTopIndex, 0);
4. 保存位图(可选)
如果需要将位图保存为文件,可使用GDI+实现(需链接gdiplus.lib):
#include <gdiplus.h> using namespace Gdiplus; // 初始化GDI+ GdiplusStartupInput gdiplusInput; ULONG_PTR gdiplusToken; GdiplusStartup(&gdiplusToken, &gdiplusInput, NULL); // 将HBITMAP转换为GDI+ Bitmap对象 Bitmap fullBitmap(hMemBitmap, NULL); // 获取PNG编码器的CLSID CLSID pngClsid; GetEncoderClsid(L"image/png", &pngClsid); // 保存为PNG文件 fullBitmap.Save(L"ListBox_Full.png", &pngClsid, NULL); // 关闭GDI+ GdiplusShutdown(gdiplusToken);
辅助函数:获取GDI+编码器CLSID
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid) { UINT encoderCount = 0; UINT encoderSize = 0; ImageCodecInfo* codecInfo = nullptr; GetImageEncodersSize(&encoderCount, &encoderSize); if (encoderSize == 0) return -1; codecInfo = (ImageCodecInfo*)malloc(encoderSize); if (!codecInfo) return -1; GetImageEncoders(encoderCount, encoderSize, codecInfo); for (UINT i = 0; i < encoderCount; ++i) { if (wcscmp(codecInfo[i].MimeType, format) == 0) { *pClsid = codecInfo[i].Clsid; free(codecInfo); return i; } } free(codecInfo); return -1; }
5. 清理资源
完成绘制后,释放创建的GDI资源:
// 恢复内存DC原有位图 SelectObject(hMemDC, hOldBitmap); // 删除位图与DC DeleteObject(hMemBitmap); DeleteDC(hMemDC); ReleaseDC(NULL, hScreenDC);
注意事项
- 确保ListBox句柄有效,且控件已完成初始化(建议在
WM_INITDIALOG或WM_CREATE之后执行)。 - 若ListBox为自绘控件,需确保控件正确处理
WM_PRINT消息(多数自定义控件会默认支持,若未支持需手动实现绘制逻辑)。 - 计算可见项数时,若ListBox显示垂直滚动条,
GetClientRect返回的客户区高度已排除滚动条宽度,无需额外调整。
内容的提问来源于stack exchange,提问作者Jumperz Ko
相关产品推荐
相关产品推荐

