首页 > 解决方案 > 在 Windows 1809 中操作系统/可见剪辑区域

问题描述

显然,微软已经改变了与 2018 年底发布的 Windows 更新 1809 的剪辑工作方式。在该更新之前GetClipBox(),即使它(部分)在屏幕外,也会返回窗口的完整客户端矩形。更新后,相同的函数返回一个裁剪矩形,只包含仍在屏幕上的部分。这会导致屏幕外区域的设备上下文内容未更新,这使我无法从这些窗口截取屏幕截图。

问题是:我可以以某种方式操纵剪辑区域吗?

我研究了一下,似乎最终的剪辑区域受到窗口区域、更新矩形和系统区域的影响——据我所知,“全局剪辑区域”。我用GetWindowRgn()和检查了窗口区域,GetRgnBox()对于 Windows 1809 和更早版本,两者都返回相同的值。GetUpdateRect()还返回完整的客户矩形,所以这也不是问题。我还尝试挂钩该BeginPaint()方法并查看更改PAINTSTRUCT.rcPaint是否有任何作用,但没有成功。

所以我剩下的就是尝试调整系统区域,或者有时称为可见区域。但是,我不知道这是否以及如何可能。MSDN 建议不是,但我想也许有人确实有解决方案的想法!?

编辑:为了更清楚地说明这一点,我不认为剪辑是由应用程序本身完成的,因为同一应用程序版本的屏幕截图在 Windows 1809 之前工作并且不适用于更新的 Windows 版本。相反,Windows 本身似乎会剪裁任何屏幕外表面。

EDIT2:这是截取屏幕截图的最小工作代码示例。

// Get the client size.
RECT crect;
GetClientRect(hwnd, &crect);
int width = crect.right - crect.left;
int height = crect.bottom - crect.top;

// Create DC and Bitmap.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = width;
bitmapInfo.bmiHeader.biHeight = -height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = width * height * 4;
char* pixels;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
HGDIOBJ previousObject = SelectObject(memoryDC, bitmap);

// Take the screenshot. Neither BitBlt nor PrintWindow work.
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
// ..or..
// PrintWindow(hwnd, memoryDC, PW_CLIENTONLY);

// Save the image.
BITMAPFILEHEADER bitmapFileHeader;
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::fstream hFile("./screenshot.bmp", std::ios::out | std::ios::binary);
if(hFile.is_open())
{
  hFile.write((char*)&bitmapFileHeader, sizeof(bitmapFileHeader));
  hFile.write((char*)&bitmapInfo.bmiHeader, sizeof(bitmapInfo.bmiHeader));
  hFile.write(pixels, (((32 * width + 31) & ~31) / 8) * height);
  hFile.close();
}

// Free Resources
ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);

您可以在此处从 Google Drive下载已编译的可执行文件。用法是Screenshot.exe <HWND>,其中 HWND 是窗口句柄的十六进制地址,例如在 Spy++ 中所示。它会将目标窗口的屏幕截图保存在工作目录中screenshot.bmp(确保您被允许写入该目录)。屏幕截图适用于几乎所有窗口(即使它们隐藏在其他窗口后面),但是一旦您将窗口部分移出屏幕,屏幕截图将继续显示窗口屏幕外部分的旧窗口内容(调整它的大小例如,它在屏幕外,以查看效果)。这只发生在 Windows 1809 上,它仍然显示早期 Windows 版本上的最新内容。

EDIT3:我对此进行了更多研究。关于WS_EX_LAYERED样式不起作用的 Adob​​eAir 应用程序:我发现它在BitBlt内部使用确实将后台缓冲区渲染到窗口 dc。渲染步骤如下:

BitBlt我在通话期间查看了系统区域。对于hdcMem系统区域是 a NULLREGION,但对于hdcWin该区域,总是在屏幕边缘被剪裁。我还尝试通过将所有调用替换为(如本文所述)来调整系统区域,GetDC但这GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN)不起作用并且似乎没有提供扩展区域的选项。我真的认为解决问题的秘诀在于操纵窗口 dc 的系统区域,但我不知道该怎么做..

如果发现该CreateDC函数将指向结构的指针DEVMODE作为最后一个参数 ( msdn )。这又具有字段dmPelsWidth和。我相信这些构成了系统区域,也许,如果我可以操纵它们,DC 将不再被剪辑,但我还不能挂钩该功能。dmPelsHeightdmPositionCreateDC

如果您有任何基于我的新见解的新想法,请分享。我会很感激任何帮助!

标签: c++windowswinapiclipping

解决方案


这似乎是 Windows 相关版本中的一个错误,显然已在更新的版本中得到修复。


推荐阅读