【问题标题】:Capturing wrong part of the screen捕获屏幕的错误部分
【发布时间】:2020-10-28 19:15:02
【问题描述】:

我创建了一个小演示应用程序,其窗口如下所示:

当我运行这个演示应用程序并按任意键时,我想捕获屏幕位图的一部分。

我感兴趣的屏幕部分,是我的窗口占据的部分,即我的窗口顶部矩形的内容,其中包含字母。捕获的屏幕位图应如下所示:

我面临的问题是屏幕捕获代码捕获了错误的屏幕部分。

下面是完整的代码(请记住,我尽量保持最小化):

#include <Windows.h>

void foo(HWND hWnd)
{
    HDC hdcScreen;
    HDC hdcWindow;

    hdcScreen = GetDC(NULL);
    hdcWindow = GetDC(hWnd);
    
    RECT rcClient;
    GetClientRect(hWnd, &rcClient);

    // map window's client coordinates to screen coordinates
    // HERE IS THE PROBLEM, SOMEHOW COORDINATES ARE NOT TRANSLATED CORRECTLY 
    // do not know how to fix this, but I am trying  :( 
    RECT rc1 = rcClient;
    MapWindowPoints(hWnd, NULL, (LPPOINT)&rc1, 2);  

    // capture desktop portion of the image
    // that corresponds to the window's top rectangle (the one that has letters in it)
    // and blit the result in the bottom rectangle
    // so result can be visually compared
    if (!BitBlt(hdcWindow, 
        rcClient.left + 50, // coordinates of the bottom rectangle 
        rcClient.top + 70,  // sorry for the "magic numbers" 
        75, 35,             // I am low on time :( 
        hdcScreen,           
        rc1.left + 50,      // screen coordinates of the top rectangle     
        rc1.top + 20,       // (the one that contains letters) 
        SRCCOPY))            
    {
        OutputDebugString(L"StretchBlt has failed");
        ReleaseDC(NULL, hdcScreen);
        ReleaseDC(hWnd, hdcWindow);
        return;
    }
    
    RECT rcBottomRect;                        // Frame again the bottom rectangle in the window,  
    rcBottomRect.left = rcClient.left + 50;   // to make visual comparing easier
    rcBottomRect.top = rcClient.top + 70;     // and to verify that I didn't screw up
    rcBottomRect.right = rcClient.left + 125; // the coordinates
    rcBottomRect.bottom = rcClient.top + 105;

    HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);
    FrameRect(hdcWindow, &rcBottomRect, br);

    ReleaseDC(NULL, hdcScreen);
    ReleaseDC(hWnd, hdcWindow);
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_KEYUP:      // easiest handler to add that keeps things minimal
        foo(hwnd);      // capture screen      
        break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        RECT rcClient;
        GetClientRect(hwnd, &rcClient);

        RECT rcTopRect;            
        rcTopRect.left = rcClient.left + 50;
        rcTopRect.top = rcClient.top + 20;
        rcTopRect.right = rcTopRect.left + 75;
        rcTopRect.bottom = rcTopRect.top + 35;

        HBRUSH br = (HBRUSH)GetStockObject(BLACK_BRUSH);

        TextOut(hdc, 20, 30, L"Asdf ghj kkl oioio 4545 676767", ARRAYSIZE(L"Asdf ghj kkl oioio 4545 676767"));
        FrameRect(hdc, &rcTopRect, br);

        RECT rcBottomRect;
        rcBottomRect.left = rcClient.left + 50;
        rcBottomRect.top = rcClient.top + 70;
        rcBottomRect.right = rcClient.left + 125;
        rcBottomRect.bottom = rcClient.top + 105;

        FrameRect(hdc, &rcBottomRect, br);

        EndPaint(hwnd, &ps);
    }
    break;
    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// BOILERPLATE CODE...
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"myWindowClass";
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Window Registration Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"myWindowClass",
        L"MVCE",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 300, 170,
        NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)
    {
        MessageBox(NULL, L"Window Creation Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Step 3: The Message Loop
    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    return Msg.wParam;
}

问题:

如何使用MapWindowPoints 修复计算“错误”(也许不是错误,也许我在滥用 API?)?

更新:

我忘了提到我有 2 台显示器。在第二台显示器上测试应用程序后一切正常。

通过第一台显示器的设置,我发现它设置为将文本、应用程序和其他项目设置为 150%。

将其恢复为 100% 使代码正常工作,但现在我需要为这种情况找到解决方案,因为我可能不会强迫用户更改他们的设置。

任何帮助将不胜感激。

【问题讨论】:

  • 您是否已验证问题与坐标有关,而不是尝试在您的 WM_PAINT 处理程序之外进行渲染?
  • @IInspectable:不,我没有,我会将代码从foo 移动到WM_PANT 并报告我的结果。
  • @IInspectable:我找到了问题,你可以在 OP 底部看到 UPDATE,但我还没有找到解决方案...
  • @AlwaysLearningNewStuff 解决方案已在答案中指出:让您的应用每台显示器了解 DPI。您可以将清单文件添加到您的项目并将其重命名为:YourAppName.exe.manifest。这个manifest文件的内容可以参考"Setting default awareness with the application manifest"
  • @RitaHan-MSFT:成功了,我只是无法决定接受哪个答案,因为双方都指出了这一点......

标签: c++ winapi gdi


【解决方案1】:

您不能强制用户更改 DPI,但您可以要求 Windows 停止在您的应用程序中弄乱坐标。为此,请将清单包含在程序的主 .exe 中。你可能需要最大。设置,true/pm 和 PerMonitorV2。

See this article 了解更多信息。

【讨论】:

    【解决方案2】:

    MapWindowPoints 的文档有点含糊,但它似乎确实需要相对于您的 window 而不是窗口的 client 区域的坐标(这就是您'正在给它)。这似乎可以解释这种症状,因为您的垂直偏移看起来与窗口标题栏的大小相同。我一直用ClientToScreen,比较清楚。

    DPI 缩放也可能是问题的根源,但它们通常不会仅出现在 y 轴上。确保将您的应用程序标记为high DPI multi-monitor aware,以便系统不会在您背后进行任何缩放。使用该设置,您可以在大部分时间使用 GDI 完成这项工作,但有一些限制。 (1)如果你的显示器有不同的缩放因子是非常困难的,并且(2)如果缩放因子在你的程序已经运行时动态改变,你可以得到通知,但是许多 API 仍然会告诉你之前的 DPI 设置改变。

    【讨论】:

    • 我赞成,接受了另一个答案,因为它链接到示例清单文件,这对我有很大帮助,因为我是新手。希望我能接受 2 个答案。无论如何,非常感谢,下次再见。
    猜你喜欢
    • 2016-02-13
    • 2012-05-24
    • 2016-03-28
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 2012-04-19
    • 1970-01-01
    • 2014-07-02
    相关资源
    最近更新 更多