c++ - 在 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
样式不起作用的 AdobeAir 应用程序:我发现它在BitBlt
内部使用确实将后台缓冲区渲染到窗口 dc。渲染步骤如下:
GetDC(hwnd)
在窗口上获得hdcWin
CreateCompatibleDC(hdcWin)
创建一个hdcMem
- 调用
SelectObject(hdcMem, bmp)
选择一个HBITMAP
进入hdcMem
BitBlt
从hdcMem
到hdcWin
. 在BitBlt
调用期间,hdcMem
即使在屏幕外区域中, 也包含有效的像素数据,但该数据永远不会复制到hdcWin
.
BitBlt
我在通话期间查看了系统区域。对于hdcMem
系统区域是 a NULLREGION
,但对于hdcWin
该区域,总是在屏幕边缘被剪裁。我还尝试通过将所有调用替换为(如本文所述)来调整系统区域,GetDC
但这GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN)
不起作用并且似乎没有提供扩展区域的选项。我真的认为解决问题的秘诀在于操纵窗口 dc 的系统区域,但我不知道该怎么做..
如果发现该CreateDC
函数将指向结构的指针DEVMODE
作为最后一个参数 ( msdn )。这又具有字段dmPelsWidth
和。我相信这些构成了系统区域,也许,如果我可以操纵它们,DC 将不再被剪辑,但我还不能挂钩该功能。dmPelsHeight
dmPosition
CreateDC
如果您有任何基于我的新见解的新想法,请分享。我会很感激任何帮助!
解决方案
这似乎是 Windows 相关版本中的一个错误,显然已在更新的版本中得到修复。
推荐阅读
- python - 如何在 Python 中创建可变代码?
- python - 如何让 Django 使用当前应用程序的模板文件夹?
- json - Couchbase 中的索引 - 使用主索引而不是二级索引
- javascript - 如何以及何时检查数组中的所有对象值是否不为空?
- swift - Swift 闭包定义为类属性不能访问其他类属性
- react-native - React Navigation - 当我在屏幕之间导航时内存增加
- javascript - 我能够检查隐藏类型变量,但我收到通过 JS 将 HTML 表单的输入类型隐藏变量发送到 PHP 文件的错误
- java - HikariCP 与 JDBC 指标 Spring Boot2
- python - 如何循环遍历每个源文件并将特定列复制到新工作簿中,每个新的“粘贴”转移到相邻列?
- linux - 使用 gstreamer 混合多个 rtp 音频流