为什么 GetClientRect 包括窗口边框和标题栏?

4

我正在使用C++编写一些代码,以给定窗口句柄拍摄另一个应用程序的屏幕截图。我使用的方法是使用BitBlt。我的应用成功地拍摄了屏幕截图,并且我有一个函数将该图像数据保存到bmp文件中。

但是屏幕截图包含了窗口的边框和标题栏。根据我的理解,GetClientRect应该排除窗口的边框和标题栏。我了解到GetWindowRect返回用户桌面内的坐标,而GetClientRect返回相对于应用程序自身的坐标。

我发现在我的屏幕截图中,标题栏和左边框是可见的,但右边框和应用程序的底部被裁剪掉了。因此,我认为如果我想要排除标题和边框,那么我需要对GetWindowRectGetClientRect进行某种组合,并使用关于窗口本身的信息来偏移GetClientRect的尺寸,例如窗口的标题栏高度。

这是否正确,或者我下面的代码有问题?

#include <Windows.h>
#include "ScreenshotManager.h"

namespace Managers {

    ScreenshotManager::ScreenshotManager(HWND gameHandle) {

        // get a device context for the window
        m_gameContext = GetWindowDC(gameHandle);

        // create a compatible device context for bitblt
        m_bitmapContext = CreateCompatibleDC(m_gameContext);

        // get window client area dimensions
        GetClientRect(gameHandle, &m_gameClientArea);

    }

    bool ScreenshotManager::TakeScreenshot() {

        // create a compatible bitmap for the game screenshots
        m_bitmap = CreateCompatibleBitmap(m_gameContext, m_gameClientArea.right, m_gameClientArea.bottom);

        // select the bitmap into the compatible device context
        SelectObject(m_bitmapContext, m_bitmap);

        // perform bit block transfer
        if (BitBlt(m_bitmapContext, 0, 0, m_gameClientArea.right, m_gameClientArea.bottom, m_gameContext, 0, 0, SRCCOPY) == false)
            return false;

        // get information about the taken screenshot
        GetObject(m_bitmap, sizeof(BITMAP), &m_bitmapInformation);

        return true;

    }

    void ScreenshotManager::SaveScreenshot(LPCWSTR outputPath) {

        BITMAPFILEHEADER   bmfHeader;    
        BITMAPINFOHEADER   bi;

        bi.biSize = sizeof(BITMAPINFOHEADER);
        bi.biWidth = m_bitmapInformation.bmWidth;
        bi.biHeight = m_bitmapInformation.bmHeight;
        bi.biPlanes = 1;    
        bi.biBitCount = 32;    
        bi.biCompression = BI_RGB;    
        bi.biSizeImage = 0;  
        bi.biXPelsPerMeter = 0;    
        bi.biYPelsPerMeter = 0;    
        bi.biClrUsed = 0;    
        bi.biClrImportant = 0;

        DWORD dwBmpSize = ((m_bitmapInformation.bmWidth * bi.biBitCount + 31) / 32) * 4 * m_bitmapInformation.bmHeight;

        // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that 
        // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc 
        // have greater overhead than HeapAlloc.
        HANDLE hDIB = GlobalAlloc(GHND,dwBmpSize);
        char *lpbitmap = (char *)GlobalLock(hDIB);

        // Gets the "bits" from the bitmap and copies them into a buffer which is pointed to by lpbitmap.
        GetDIBits(m_gameContext, m_bitmap, 0, (UINT)m_bitmapInformation.bmHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS);

        // A file is created, this is where we will save the screen capture.
        HANDLE hFile = CreateFile(outputPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);   

        // Add the size of the headers to the size of the bitmap to get the total file size
        DWORD dwSizeofDIB = dwBmpSize + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

        //Offset to where the actual bitmap bits start.
        bmfHeader.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER); 

        //Size of the file
        bmfHeader.bfSize = dwSizeofDIB; 

        //bfType must always be BM for Bitmaps
        bmfHeader.bfType = 0x4D42; //BM   

        DWORD dwBytesWritten = 0;
        WriteFile(hFile, (LPSTR)&bmfHeader, sizeof(BITMAPFILEHEADER), &dwBytesWritten, NULL);
        WriteFile(hFile, (LPSTR)&bi, sizeof(BITMAPINFOHEADER), &dwBytesWritten, NULL);
        WriteFile(hFile, (LPSTR)lpbitmap, dwBmpSize, &dwBytesWritten, NULL);

        //Unlock and Free the DIB from the heap
        GlobalUnlock(hDIB);    
        GlobalFree(hDIB);

        //Close the handle for the file that was created
        CloseHandle(hFile);

    }

}

“PrintWindow”可以为您节省一些工作量,并且在最小化时也可以使用。 - chris
我排除了PrintWindow函数,因为根据文档,它似乎将屏幕截图的实际处理推迟到您正在截图的程序中。这不是比BitBlt方法更不兼容吗? - Ryan
我还没有深入研究过,但它确实具有最小化窗口的优点。如果您担心,可以在“TakeScreenshot”函数中检查它是否被最小化,并选择要使用的方法。 - chris
你确定坐标是错误的吗?也许程序正在做一些奇怪的事情。请使用Spy++进行检查。 - Luke
1个回答

9
GetClientRect()方法不包括边框和标题栏。它只告诉你客户区域的尺寸。 BitBlt()方法将一个矩形像素区域从一个设备上下文复制到另一个设备上下文中。在这个例子中,源DC是一个窗口DC,因此原点坐标是相对于该窗口的。
你的代码所做的是从窗口的原点复制一个客户大小的矩形。(这就是为什么右侧和底部会丢失的原因。)
你可能会对AdjustWindowRectEx()感兴趣,以帮助确定您要复制的区域的坐标。

1
好答案。关于你的最后一个声明,怎么使用AdjustWindowRectEx来确定那些坐标? - Dan
1
AdjustWindowRectEx用于反向操作,即扩展客户端矩形以考虑非客户区域(有时称为chrome)。一种方法是创建一个点(0,0),并使用ClientToScreen进行转换。然后使用GetWindowRect并将转换后的点与窗口矩形的左侧和顶部进行比较。差异将告诉您需要偏移源以进行BitBlt的距离。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接