|
|
发表于 2026-1-29 14:16:28
|
显示全部楼层
**
该代码基于Win32 SDK开发,适配 Windows 10 1703+(支持每窗口 DPI 感知)
实现工具界面跟随系统 DPI 缩放(150%/200% 等)、截图功能高 DPI 感知不失真,包含完整的截图逻辑、UI 界面(截图按钮)、DPI 隔离处理,可直接在 Visual Studio 编译运行
主界面自动跟随系统 DPI 缩放,无界面变小 / 布局错乱问题;
独立截图线程,临时切换 DPI 感知模式,截图获取屏幕真实分辨率,完全不失真;
截图自动保存到桌面,格式为 BMP(无压缩,保证画质);
嵌入 Manifest 配置,无需单独创建配置文件,编译即可用;
资源自动释放,无内存泄漏 / GDI 句柄泄漏问题。
// 头文件包含(Win32基础+GDI绘图+控件)
#include <windows.h>
#include <commctrl.h>
#include <shlobj.h>
#include <string>
#include <ctime>
#pragma comment(lib, "user32.lib") // 窗口/控件相关库
#pragma comment(lib, "gdi32.lib") // 截图/GDI绘图相关库
#pragma comment(lib, "comctl32.lib") // 通用控件库
#pragma comment(lib, "shell32.lib") // 获取桌面路径库
// ************************** 关键:嵌入DPI感知Manifest配置 **************************
// 声明PerMonitorV2 DPI感知,让窗口支持每屏幕独立DPI缩放,替代手动SetProcessDPIAware
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#pragma comment(linker, "/manifest:\"<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'><application xmlns='urn:schemas-microsoft-com:asm.v3'><windowsSettings><dpiAware xmlns='http://schemas.microsoft.com/SMI/2005/WindowsSettings'>true/PM</dpiAware><dpiAwareness xmlns='http://schemas.microsoft.com/SMI/2016/WindowsSettings'>PerMonitorV2,PerMonitor</dpiAwareness></windowsSettings></application></assembly>\"")
// 全局常量:窗口/控件ID
#define IDC_BTN_CAPTURE 1001 // 截图按钮ID
#define WND_WIDTH 300 // 窗口宽度(逻辑像素,自动适配DPI)
#define WND_HEIGHT 120 // 窗口高度(逻辑像素,自动适配DPI)
#define BTN_WIDTH 180 // 按钮宽度(逻辑像素)
#define BTN_HEIGHT 40 // 按钮高度(逻辑像素)
// 窗口过程声明
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 截图线程函数声明(核心:独立线程处理截图,隔离DPI感知)
DWORD WINAPI CaptureScreenThread(LPVOID lpParam);
// 辅助函数:获取桌面路径
std::wstring GetDesktopPath();
// 辅助函数:根据DPI缩放像素值(逻辑像素→物理像素)
int ScaleByDpi(int nLogical, UINT dpi);
// 程序入口
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 初始化通用控件(必须,否则按钮显示异常)
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_STANDARD_CLASSES;
InitCommonControlsEx(&icex);
// 窗口类注册
const wchar_t CLASS_NAME[] = L"DpiAwareCaptureToolClass";
WNDCLASS wc = {0};
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 窗口背景色
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 鼠标光标
if (!RegisterClass(&wc))
{
MessageBox(NULL, L"窗口类注册失败!", L"错误", MB_ICONERROR);
return 0;
}
// 创建主窗口(居中显示)
int screenW = GetSystemMetrics(SM_CXSCREEN);
int screenH = GetSystemMetrics(SM_CYSCREEN);
HWND hWnd = CreateWindowEx(
0,
CLASS_NAME,
L"DPI自适应截图工具(保存到桌面)", // 窗口标题
WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, // 窗口样式(禁止最大化)
(screenW - WND_WIDTH) / 2, // 窗口X坐标(居中)
(screenH - WND_HEIGHT) / 2, // 窗口Y坐标(居中)
WND_WIDTH, // 窗口宽度
WND_HEIGHT, // 窗口高度
NULL,
NULL,
hInstance,
NULL
);
if (!hWnd)
{
MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR);
return 0;
}
// 显示并更新窗口
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// 消息循环(Win32程序核心)
MSG msg = {0};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// 窗口过程函数:处理所有窗口消息(创建控件、按钮点击、窗口关闭等)
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HINSTANCE hInst; // 实例句柄
switch (uMsg)
{
case WM_CREATE: // 窗口创建时触发(初始化控件、DPI设置)
{
hInst = ((LPCREATESTRUCT)lParam)->hInstance;
// ************************** 核心:设置线程DPI感知为PerMonitorV2 **************************
// 让当前窗口线程支持每窗口DPI感知,界面自动跟随系统/显示器DPI缩放
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
// 获取当前窗口DPI(用于缩放控件,适配不同DPI比例)
UINT dpi = GetDpiForWindow(hWnd);
// 计算控件物理坐标(逻辑像素→物理像素,适配DPI)
int btnX = ScaleByDpi((WND_WIDTH - BTN_WIDTH) / 2, dpi);
int btnY = ScaleByDpi((WND_HEIGHT - BTN_HEIGHT) / 2, dpi);
int btnW = ScaleByDpi(BTN_WIDTH, dpi);
int btnH = ScaleByDpi(BTN_HEIGHT, dpi);
// 创建截图按钮
CreateWindow(
L"BUTTON", // 控件类名(按钮)
L"点击截取全屏(保存到桌面)", // 按钮文字
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, // 按钮样式
btnX, btnY, btnW, btnH, // 按钮坐标和尺寸(物理像素)
hWnd,
(HMENU)IDC_BTN_CAPTURE, // 按钮ID
hInst,
NULL
);
break;
}
case WM_COMMAND: // 控件消息(按钮点击)
{
if (LOWORD(wParam) == IDC_BTN_CAPTURE && HIWORD(wParam) == BN_CLICKED)
{
// ************************** 核心:创建独立截图线程 **************************
// 避免截图阻塞UI,同时隔离DPI感知模式
HANDLE hThread = CreateThread(
NULL, // 安全属性(默认)
0, // 栈大小(默认)
CaptureScreenThread, // 截图线程函数
(LPVOID)hWnd, // 传递给线程的参数(主窗口句柄,用于弹窗提示)
0, // 创建标志(立即运行)
NULL // 线程ID(无需获取)
);
// 关闭线程句柄(释放资源,不影响线程运行)
if (hThread) CloseHandle(hThread);
}
break;
}
case WM_DPICHANGED: // DPI变化时触发(如拖动窗口到不同DPI显示器)
{
// 调整窗口大小,适配新的DPI
RECT* pNewRect = (RECT*)lParam;
SetWindowPos(hWnd, NULL, pNewRect->left, pNewRect->top,
pNewRect->right - pNewRect->left, pNewRect->bottom - pNewRect->top,
SWP_NOZORDER | SWP_NOACTIVATE);
break;
}
case WM_CLOSE: // 窗口关闭时触发
DestroyWindow(hWnd);
break;
case WM_DESTROY: // 窗口销毁时触发
PostQuitMessage(0); // 退出消息循环
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
// ************************** 核心:截图线程函数 **************************
// 独立线程处理截图,临时切换为系统DPI感知,完成后恢复,保证截图不失真
DWORD WINAPI CaptureScreenThread(LPVOID lpParam)
{
HWND hMainWnd = (HWND)lpParam; // 主窗口句柄(用于弹窗提示)
BOOL bCaptureOk = FALSE; // 截图是否成功标志
// ************************** 关键1:保存原线程DPI感知上下文 **************************
DPI_AWARENESS_CONTEXT oldDpiContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
if (oldDpiContext == NULL)
{
MessageBox(hMainWnd, L"切换DPI感知模式失败!", L"错误", MB_ICONERROR);
return 0;
}
do
{
// ************************** 关键2:获取屏幕真实DC和分辨率(系统DPI下,无缩放) **************************
HDC hScreenDC = GetDC(NULL); // 获取整个屏幕的DC(NULL表示全屏)
if (hScreenDC == NULL) break;
// 获取屏幕真实分辨率(物理像素,无DPI缩放,保证截图完整)
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
// 创建兼容DC和位图(用于存储截图数据)
HDC hMemDC = CreateCompatibleDC(hScreenDC);
HBITMAP hBmp = CreateCompatibleBitmap(hScreenDC, nScreenWidth, nScreenHeight);
if (hMemDC == NULL || hBmp == NULL) break;
// 保存兼容DC的原始位图
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hBmp);
// ************************** 执行截图:将屏幕DC绘制到兼容DC **************************
BitBlt(
hMemDC, 0, 0, nScreenWidth, nScreenHeight,
hScreenDC, 0, 0,
SRCCOPY // 复制模式(完整拷贝,无修改)
);
// ************************** 将截图保存为BMP文件到桌面 **************************
// 获取BMP文件信息
BITMAP bmpInfo = {0};
GetObject(hBmp, sizeof(bmpInfo), &bmpInfo);
BITMAPFILEHEADER bmpFileHeader = {0};
BITMAPINFOHEADER bmpInfoHeader = {0};
// 填充BMP文件头
bmpFileHeader.bfType = 0x4D42; // BMP文件标识("BM")
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmpInfo.bmWidthBytes * bmpInfo.bmHeight;
bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// 填充BMP信息头
bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfoHeader.biWidth = bmpInfo.bmWidth;
bmpInfoHeader.biHeight = bmpInfo.bmHeight;
bmpInfoHeader.biPlanes = 1;
bmpInfoHeader.biBitCount = bmpInfo.bmBitsPixel;
bmpInfoHeader.biCompression = BI_RGB; // 无压缩
// 获取桌面路径,拼接截图文件名(按时间命名,避免重复)
std::wstring strDesktop = GetDesktopPath();
time_t now = time(NULL);
tm t = {0};
localtime_s(&t, &now);
wchar_t strFileName[256] = {0};
swprintf_s(strFileName, L"%s\\屏幕截图_%04d%02d%02d_%02d%02d%02d.bmp",
strDesktop.c_str(),
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec);
// 写入BMP文件
HANDLE hFile = CreateFile(strFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
DWORD dwWrite = 0;
WriteFile(hFile, &bmpFileHeader, sizeof(bmpFileHeader), &dwWrite, NULL);
WriteFile(hFile, &bmpInfoHeader, sizeof(bmpInfoHeader), &dwWrite, NULL);
WriteFile(hFile, bmpInfo.bmBits, bmpInfo.bmWidthBytes * bmpInfo.bmHeight, &dwWrite, NULL);
CloseHandle(hFile);
bCaptureOk = TRUE; // 截图成功
}
// ************************** 释放GDI资源(必须,避免泄漏) **************************
SelectObject(hMemDC, hOldBmp); // 恢复原始位图
DeleteObject(hBmp); // 删除截图位图
DeleteDC(hMemDC); // 删除兼容DC
ReleaseDC(NULL, hScreenDC); // 释放屏幕DC
} while (FALSE);
// ************************** 关键3:恢复原线程DPI感知上下文 **************************
SetThreadDpiAwarenessContext(oldDpiContext);
// 弹窗提示截图结果
if (bCaptureOk)
{
MessageBox(hMainWnd, L"截图成功!已保存到桌面!", L"成功", MB_ICONINFORMATION);
}
else
{
MessageBox(hMainWnd, L"截图失败!", L"错误", MB_ICONERROR);
}
return 0;
}
// 辅助函数:获取桌面路径(宽字符版,避免中文乱码)
std::wstring GetDesktopPath()
{
wchar_t szPath[MAX_PATH] = {0};
SHGetSpecialFolderPathW(NULL, szPath, CSIDL_DESKTOP, FALSE);
return std::wstring(szPath);
}
// 辅助函数:根据DPI缩放像素值(逻辑像素→物理像素)
// 基础DPI为96(100%缩放),公式:物理像素 = 逻辑像素 * (当前DPI / 96)
int ScaleByDpi(int nLogical, UINT dpi)
{
return MulDiv(nLogical, dpi, 96);
}
|
|