windows 在C++中新建的窗口中显示屏幕截图

yws3nbqq  于 2023-05-01  发布在  Windows
关注(0)|答案(2)|浏览(201)

我做了一个程序,采取屏幕截图,并保存在本地文件夹(这工作)。我删除了这部分,并希望在新创建的窗口中显示新拍摄的屏幕截图。一切都很好,截图被采取,窗口被创建,但唯一的问题是,窗口不显示采取的截图。
如果您想在Visual Studio中运行代码,则必须将Properties -> Linker -> System -> SubSystem (in Visual Studio)Windows (/SUBSYSTEM:WINDOWS)更改为Console (/SUBSYSTEM:CONSOLE)
如何在新创建的窗口中显示新拍摄的屏幕截图?
这就是代码:

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>
#include <objidl.h>
#include <stdio.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT num = 0;
    UINT size = 0;
    Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;

    Gdiplus::GetImageEncodersSize(&num, &size);
    if (size == 0) {
        printf("Error: GetImageEncodersSize returned size 0\n");
        return -1;
    }

    pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL) {
        printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
        return -1;
    }

    if (GetImageEncoders(num, size, pImageCodecInfo) != Ok) {
        printf("Error: GetImageEncoders failed\n");
        free(pImageCodecInfo);
        return -1;
    }

    for (UINT j = 0; j < num; ++j) {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0) {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;
        }
    }

    free(pImageCodecInfo);
    return -1;
}

void TakeScreenshot()
{
    // Get the dimensions of the whole desktop
    int dpi_x = GetDpiForSystem();
    int dpi_y = GetDpiForSystem();
    int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
    int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);

    if (width == 0 || height == 0) {
        printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
        return;
    }

    // Create a bitmap to hold the screenshot
    HDC screen_dc = GetDC(NULL);
    if (screen_dc == NULL) {
        printf("Error: GetDC failed to get a handle to the screen device context\n");
        return;
    }

    HDC mem_dc = CreateCompatibleDC(screen_dc);
    if (mem_dc == NULL) {
        printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
        ReleaseDC(NULL, screen_dc);
        return;
    }

    // Create a bitmap that is scaled to the appropriate DPI
    HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
    if (bitmap == NULL) {
        printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
        DeleteDC(mem_dc);
        ReleaseDC(NULL, screen_dc);
        return;
    }

    HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);

    // Set the DPI of the memory DC to match the system DPI
    SetGraphicsMode(mem_dc, GM_ADVANCED);
    XFORM xform;
    xform.eM11 = (FLOAT)dpi_x / 96;
    xform.eM12 = xform.eM21 = xform.eM22 = 0;
    xform.eDx = xform.eDy = 0;
    SetWorldTransform(mem_dc, &xform);

    // Copy the screen contents to the bitmap
    if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0) {
        printf("Error:BitBlt failed to copy screen contents to bitmap\n");
        return;
    }

    // Save the bitmap to a file
    Gdiplus::Bitmap image(bitmap, NULL);
    if (image.GetLastStatus() != Ok) {
        printf("Error: Bitmap constructor failed to create a Bitmap object\n");
        return;
    }

    CLSID png_clsid;
    int r = GetEncoderClsid(L"image/png", &png_clsid);
    if (r == -1)
    {
        printf("Error: unable to find image encoder for MIME type 'image/png'\n");
        return;
    }

    HWND hwnd;
    WNDCLASSEX wc;
    HINSTANCE hInstance = GetModuleHandle(NULL);

    // Define window class
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = DefWindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"ScreenshotWindowClass";
    RegisterClassEx(&wc);

    // Create window
    hwnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"ScreenshotWindowClass",
        L"Screenshot",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, width, height,
        NULL, NULL, hInstance, NULL);

    if (hwnd == NULL) {
        printf("Error: CreateWindowEx failed to create window\n");
        return;
    }

    // Show screenshot in window
    HDC hdc = GetDC(hwnd);
    Graphics graphics(hdc);
    graphics.DrawImage(&image, 0, 0, width, height);
    ReleaseDC(hwnd, hdc);

    // Display window
    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // Wait for user to close window
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Clean up
    UnregisterClass(L"ScreenshotWindowClass", hInstance);

    // Clean up
    SelectObject(mem_dc, old_bitmap);
    DeleteObject(bitmap);
    DeleteDC(mem_dc);
    ReleaseDC(NULL, screen_dc);
}

int main()
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);

    TakeScreenshot();

    SetThreadDpiAwarenessContext(dpi_awareness_context);

    GdiplusShutdown(gdiplusToken);
    return 0;
}
dauxcl2d

dauxcl2d1#

这里的核心问题是试图“从外部”呈现到窗口上。如果没有目标窗口的配合,显示窗口内容是很困难的。你应该做的是让窗口自己处理显示。
注册窗口类时,分配给lpfnWndProc的函数指针实现了窗口的行为。在这种简单的情况下,只有两条消息需要显式处理:WM_PAINT(负责渲染客户端区域)和WM_DESTROY。后者是必需的,这样我们就可以通过发布WM_QUIT线程消息来打破以下消息循环。
下面的实现也处理WM_CREATE,以便将位图句柄传递到CreateWindowEx中,并将其存储在每个窗口的内存中,以方便访问。

  • ScreenshotWndProc:*
LRESULT CALLBACK ScreenshotWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_CREATE: {
        // Read `HBITMAP` from `CREATESTRUCT`
        auto const params = reinterpret_cast<CREATESTRUCT const*>(lParam)->lpCreateParams;
        auto const bitmap = reinterpret_cast<HBITMAP>(params);
        // Store the bitmap at index 0
        ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(bitmap));
        // Continue window creation
        return 0;
    }

    case WM_DESTROY:
        ::PostQuitMessage(0);
        break;

    case WM_PAINT: {
        PAINTSTRUCT ps = {};
        HDC hdc = ::BeginPaint(hWnd, &ps);

        // Get client size
        RECT rc = {};
        ::GetClientRect(hWnd, &rc);
        auto const width = rc.right - rc.left;
        auto const height = rc.bottom - rc.top;

        // Retrieve bitmap from window
        auto const bitmap = reinterpret_cast<HBITMAP const>(::GetWindowLongPtr(hWnd, 0));

        // Create a memory device context to blit from
        auto const src_dc = ::CreateCompatibleDC(hdc);
        auto const prev_bmp = ::SelectObject(src_dc, bitmap);

        // Blit bitmap to client area
        ::BitBlt(hdc, 0, 0, width, height, src_dc, 0, 0, SRCCOPY);

        // Clean up memory device context
        ::SelectObject(src_dc, prev_bmp);
        ::DeleteDC(src_dc);

        ::EndPaint(hWnd, &ps);

        // Message has been processed
        return 0;
    }

    default:
        break;
    }

    return ::DefWindowProc(hWnd, Msg, wParam, lParam);
}
  • 调整窗口类注册和窗口创建:*
WNDCLASSEX wc;
    HINSTANCE hInstance = GetModuleHandle(NULL);

    // Define window class
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = ScreenshotWndProc;
    wc.cbWndExtra = sizeof(HBITMAP); // reserve memory for one `HBITMAP`
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"ScreenshotWindowClass";
    RegisterClassEx(&wc);

    // Create window
    hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"ScreenshotWindowClass", L"Screenshot", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
                          CW_USEDEFAULT, width, height, NULL, NULL, hInstance, bitmap);

由于该设计依赖于保持对屏幕截图位图的引用的中间窗口,因此重要的是该位图不再被选择到任何设备上下文中,并且也比窗口更长。这需要重新安排部分资源管理代码,所以这里是完整的示例代码供参考:

#define _CRT_SECURE_NO_WARNINGS

#include <windows.h>

#include <objidl.h>

#include <stdio.h>

#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment(lib, "Gdiplus.lib")

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT num = 0;
    UINT size = 0;
    Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;

    Gdiplus::GetImageEncodersSize(&num, &size);
    if (size == 0)
    {
        printf("Error: GetImageEncodersSize returned size 0\n");
        return -1;
    }

    pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL)
    {
        printf("Error: malloc failed to allocate memory for ImageCodecInfo\n");
        return -1;
    }

    if (GetImageEncoders(num, size, pImageCodecInfo) != Ok)
    {
        printf("Error: GetImageEncoders failed\n");
        free(pImageCodecInfo);
        return -1;
    }

    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;
        }
    }

    free(pImageCodecInfo);
    return -1;
}

LRESULT CALLBACK ScreenshotWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
    switch (Msg)
    {
    case WM_CREATE: {
        // Read `HBITMAP` from `CREATESTRUCT`
        auto const params = reinterpret_cast<CREATESTRUCT const*>(lParam)->lpCreateParams;
        auto const bitmap = reinterpret_cast<HBITMAP>(params);
        // Store the bitmap at index 0
        ::SetWindowLongPtr(hWnd, 0, reinterpret_cast<LONG_PTR>(bitmap));
        // Continue window creation
        return 0;
    }

    case WM_DESTROY:
        ::PostQuitMessage(0);
        break;

    case WM_PAINT: {
        PAINTSTRUCT ps = {};
        HDC hdc = ::BeginPaint(hWnd, &ps);

        // Get client size
        RECT rc = {};
        ::GetClientRect(hWnd, &rc);
        auto const width = rc.right - rc.left;
        auto const height = rc.bottom - rc.top;

        // Retrieve bitmap from window
        auto const bitmap = reinterpret_cast<HBITMAP const>(::GetWindowLongPtr(hWnd, 0));

        // Create a memory device context to blit from
        auto const src_dc = ::CreateCompatibleDC(hdc);
        auto const prev_bmp = ::SelectObject(src_dc, bitmap);

        // Blit bitmap to client area
        ::BitBlt(hdc, 0, 0, width, height, src_dc, 0, 0, SRCCOPY);

        // Clean up memory device context
        ::SelectObject(src_dc, prev_bmp);
        ::DeleteDC(src_dc);

        ::EndPaint(hWnd, &ps);

        // Message has been processed
        return 0;
    }

    default:
        break;
    }

    return ::DefWindowProc(hWnd, Msg, wParam, lParam);
}

void TakeScreenshot()
{
    // Get the dimensions of the whole desktop
    int dpi_x = GetDpiForSystem();
    int dpi_y = GetDpiForSystem();
    int width = GetSystemMetricsForDpi(SM_CXSCREEN, dpi_x);
    int height = GetSystemMetricsForDpi(SM_CYSCREEN, dpi_y);

    if (width == 0 || height == 0)
    {
        printf("Error: GetSystemMetrics returned invalid screen dimensions\n");
        return;
    }

    // Create a bitmap to hold the screenshot
    HDC screen_dc = GetDC(NULL);
    if (screen_dc == NULL)
    {
        printf("Error: GetDC failed to get a handle to the screen device context\n");
        return;
    }

    HDC mem_dc = CreateCompatibleDC(screen_dc);
    if (mem_dc == NULL)
    {
        printf("Error: CreateCompatibleDC failed to create a compatible device context\n");
        ReleaseDC(NULL, screen_dc);
        return;
    }

    // Create a bitmap that is scaled to the appropriate DPI
    HBITMAP bitmap = CreateBitmap(width, height, 1, GetDeviceCaps(screen_dc, BITSPIXEL), NULL);
    if (bitmap == NULL)
    {
        printf("Error: CreateCompatibleBitmap failed to create a compatible bitmap\n");
        DeleteDC(mem_dc);
        ReleaseDC(NULL, screen_dc);
        return;
    }

    HGDIOBJ old_bitmap = SelectObject(mem_dc, bitmap);

    // Set the DPI of the memory DC to match the system DPI
    SetGraphicsMode(mem_dc, GM_ADVANCED);
    XFORM xform;
    xform.eM11 = (FLOAT)dpi_x / 96;
    xform.eM12 = xform.eM21 = xform.eM22 = 0;
    xform.eDx = xform.eDy = 0;
    SetWorldTransform(mem_dc, &xform);

    // Copy the screen contents to the bitmap
    if (BitBlt(mem_dc, 0, 0, width, height, screen_dc, 0, 0, SRCCOPY) == 0)
    {
        printf("Error:BitBlt failed to copy screen contents to bitmap\n");
        return;
    }

    // Select `bitmap` out of memory device context
    SelectObject(mem_dc, old_bitmap);
    // We're done with the DC's, so clean them up
    DeleteDC(mem_dc);
    ReleaseDC(NULL, screen_dc);

    // Save the bitmap to a file
    Gdiplus::Bitmap image(bitmap, NULL);
    if (image.GetLastStatus() != Ok)
    {
        printf("Error: Bitmap constructor failed to create a Bitmap object\n");
        return;
    }

    CLSID png_clsid;
    int r = GetEncoderClsid(L"image/png", &png_clsid);
    if (r == -1)
    {
        printf("Error: unable to find image encoder for MIME type 'image/png'\n");
        return;
    }

    HWND hwnd;
    WNDCLASSEX wc;
    HINSTANCE hInstance = GetModuleHandle(NULL);

    // Define window class
    ZeroMemory(&wc, sizeof(WNDCLASSEX));
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = ScreenshotWndProc;
    wc.cbWndExtra = sizeof(HBITMAP); // reserve memory for one `HBITMAP`
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.lpszClassName = L"ScreenshotWindowClass";
    RegisterClassEx(&wc);

    // Create window
    hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"ScreenshotWindowClass", L"Screenshot", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
                          CW_USEDEFAULT, width, height, NULL, NULL, hInstance, bitmap);

    if (hwnd == NULL)
    {
        printf("Error: CreateWindowEx failed to create window\n");
        return;
    }

    // Display window
    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // Wait for user to close window
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Make sure the `bitmap` lives as long the window
    DeleteObject(bitmap);

    // Clean up
    UnregisterClass(L"ScreenshotWindowClass", hInstance);
}

int main()
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    DPI_AWARENESS_CONTEXT dpi_awareness_context = SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);

    TakeScreenshot();

    SetThreadDpiAwarenessContext(dpi_awareness_context);

    GdiplusShutdown(gdiplusToken);
    return 0;
}

顺便说一句:传入CreateWindowExnWidthnHeight参数指定 window 大小。代码表明,您实际需要的是一个具有所需 client 大小的窗口。您可以调用AdjustWindowRectEx来计算与给定客户端矩形对应的窗口矩形。

n6lpvg4x

n6lpvg4x2#

根据When to Draw in a Window,在WM_PAINT之外进行绘制是可行的。
如果应用程序在任何其他时间进行绘制,例如从WinMain内或在处理键盘或鼠标消息期间,它将调用GetDCGetDCEx函数来检索显示DC。
正如@IgorTandetnik昨天解释的那样,可能是由于绘画发生在ShowWindow之前,它被删除是因为ShowWindow绘制了默认的白色背景。下面的代码适合我。

// Display window
    ShowWindow(hwnd, nCmdShow);

    // Show screenshot in window
    HDC hdc = GetDC(hwnd);
    Graphics graphics(hdc);
    graphics.DrawImage(&image, 0, 0, width, height);
    ReleaseDC(hwnd, hdc);

相关问题