开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 419|回复: 5
收起左侧

[精币悬赏] DPI问题

[复制链接]
结帖率:92% (58/63)
发表于 2025-12-10 16:31:41 | 显示全部楼层 |阅读模式   广东省佛山市
500精币
WIN系统缩放与布局为150%,SetProcessDPIAware ()可以正常截图,但截图时截图工具界面显示为100%显得界面很小,字体都显示小看得不舒服。是否能做到截图工具界面在截图时不随SetProcessDPIAware ()而变化,保持与系统的设置一致。

结帖率:100% (12/12)

签到天数: 3 天

发表于 2025-12-10 16:44:27 | 显示全部楼层   山东省济宁市
用自绘可以解决 我之前也找了很多方法 最后还是自绘最好用
回复

使用道具 举报

结帖率:87% (73/84)
发表于 2025-12-10 17:39:46 | 显示全部楼层   四川省成都市
图色没办法
回复

使用道具 举报

结帖率:87% (73/84)
发表于 2025-12-10 17:40:28 | 显示全部楼层   四川省成都市
就算你你这样截图,后续分辨率不一样,也用不了,所以最稳定只有100%
回复

使用道具 举报

结帖率:100% (1/1)
发表于 2026-1-29 14:13:21 | 显示全部楼层   **
修改 Manifest 文件
在程序的manifest文件中声明 DPI 感知模式,比代码调用更可靠,可结合窗口级设置:
<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>
截图线程单独设置 DPI 感知
创建独立的截图线程,在该线程中临时切换到系统 DPI 感知模式,完成截图后再恢复:
// 截图线程函数
DWORD WINAPI CaptureThread(LPVOID lpParam) {
    // 保存原线程的DPI感知上下文
    DPI_AWARENESS_CONTEXT oldContext = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
   
    // 执行截图操作(示例用BitBlt)
    HDC hScreenDC = GetDC(NULL);
    HDC hMemDC = CreateCompatibleDC(hScreenDC);
    // ... 你的截图逻辑 ...
    ReleaseDC(NULL, hScreenDC);
    DeleteDC(hMemDC);
   
    // 恢复线程原来的DPI感知
    SetThreadDpiAwarenessContext(oldContext);
    return 0;
}

// 触发截图时创建线程
CreateThread(NULL, 0, CaptureThread, NULL, 0, NULL);
主窗口 DPI 感知设置
不调用全局的 SetProcessDPIAware(),而是在主窗口初始化时,为窗口所在线程设置每窗口 DPI 感知 V2 模式,让界面跟随系统 150% 缩放:
// 主窗口初始化时调用
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

回复

使用道具 举报

结帖率:100% (1/1)
发表于 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);
}

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报QQ: 793400750,邮箱:wp@125.la
网站简介:精易论坛成立于2009年,是一个程序设计学习交流技术论坛,隶属于揭阳市揭东区精易科技有限公司所有。
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备2025452707号) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表