捕获窗口的屏幕截图,不包括某些 HWND 的样式 (WS_SYSMENU)

Posted

技术标签:

【中文标题】捕获窗口的屏幕截图,不包括某些 HWND 的样式 (WS_SYSMENU)【英文标题】:Capturing window's screenshot excluding certain HWND's styles (WS_SYSMENU) 【发布时间】:2018-05-16 22:02:42 【问题描述】:

我正在尝试每隔几秒定期比较同一窗口的快照。即使从技术上讲窗口没有改变,图片保持不变,我仍然会收到通知图片中的某些内容发生了变化,我正在使用旧的BitBlt 函数来捕获特定的窗口。和Zlib's CRC32 比较结果。

这是 2 张相同图片的示例,唯一的区别是窗口的标题 (untitled paint) 颜色。只要窗口有焦点,它就是黑色的,否则是灰色的。由于我不是 HWND 的所有者,有没有比更改样式更好的方法来拍照而不计算窗口的实际大小减去 GetSystemMetrices(SM_CYSIZEFRAME / SM_CXSIZE)。

我的代码:

WINDOWPLACEMENT rect;
    ::GetWindowPlacement(windowDesc.hWnd, &rect);
    if (SW_SHOWMINIMIZED == rect.showCmd)
    
        return;
    

    CImage img;
    img.Create(
        rect.rcNormalPosition.right - rect.rcNormalPosition.left,
        rect.rcNormalPosition.bottom - rect.rcNormalPosition.top, 
        32);

    HWND hWnd = windowDesc.hWnd;
    std::shared_ptr<HDC__> spSrcHdc(::GetDC(hWnd), [hWnd](HDC hdc) ::ReleaseDC(hWnd, hdc); );
    //::BitBlt(img.GetDC(), 0, 0, img.GetWidth(), img.GetHeight(), spSrcHdc.get(), 0, 0, SRCCOPY);
    ::PrintWindow(hWnd, img.GetDC(), 0x2);
    BITMAP bmp =  0 ;
    if (!::GetObject((HBITMAP)img, sizeof(BITMAP), &bmp))
    
        throw std::exception("Failed to retrieve raw bmp buffer");
    

    unsigned long ulbmpCRC = crc32(0, 
        (BYTE*)bmp.bmBits, bmp.bmWidthBytes * bmp.bmHeight);

    if (0 != ulbmpCRC && ulbmpCRC == windowDesc.crc)
    
    

我未能解决的另一个问题是文本框出现时。事实上,我的光标闪烁,它会生成不同的 CRC32 值。再一次,我可以使用具有忽略这种现象的能力的 BitBlt 吗?

【问题讨论】:

没有通用的方法来做你想做的事。主题 UI 的系统度量值是错误的,您也不能简单地要求客户矩形,因为某些应用程序(如 Visual Studio)不使用标准的非客户区。虽然这听起来像是XY Problem,但您最终想要完成什么? 我想在图片发生变化时保存屏幕截图。虽然前面提到的其实是变化。每当绘图本身发生变化时,我想截屏(保存图片) 您是在特别询问 MS Paint 吗?但是,无论哪种方式,您的方法都是脆弱的。即使您识别绘图区域并比较屏幕截图,只要用户使用滚动条移动绘图,您也会得到误报。或者只是更改缩放级别。或者启用/禁用像素网格。 不是mspaint,通用解决方案。我只想排除系统菜单更改和文本框控件内的一般光标闪烁 您的方法存在根本缺陷。这是行不通的。曾经。使用您提出的算法,每当“有趣区域”的一部分被另一个窗口遮挡或用户决定调整视口大小时,您都会收到更改通知。对于已请求其显示关联为WDA_MONITOR 的窗口或使用 DirectX 渲染表面的窗口,它将失败。你不妨把时间花在解决一个有解决方案的问题上。这不是其中之一。 【参考方案1】:

要确定目标窗口的客户区,您可以向其发送WM_NCCALCSIZE 消息。这应该使您能够可靠地确定大多数应用程序的标题栏大小,例如:

RECT r;
GetWindowRect (hTargetWnd, &r);
SendMessage (hTargetWnd, WM_NCCALCSIZE, FALSE, (LPARAM) &r);

至于闪烁的插入符号问题,您可以尝试记住您看到的最后一个两个不同屏幕的校验和。然后您就有机会确定窗口实际上何时在两种状态之间来回切换。

这样,以及更复杂的比较逻辑(例如遍历子窗口列表以查找编辑控件以查看是否有任何更改仅限于此类控件的内容),应该可以实现您想要的大部分内容.

【讨论】:

这假定,windows 将实际使用系统服务进行非客户区管理。据我所知,Visual Studio 没有。 UWP 应用程序当然不会,因为它们甚至没有使用HWNDs 进行演示。这个答案甚至几乎没有解决 OP 已经确定的问题。我发布了关于 OP 甚至没有考虑过的问题的 cmets。 是的,我知道很多应用在绘制非客户区的时候都在玩游戏,而你提出了一个关于 UWP 应用的好观点,我对这些不太了解。尽管启用了 DWM 组合,您可能能够成功地从部分覆盖的窗口中 bitblt,但我最近没有测试过。最后,我想很大程度上取决于 OP 是否愿意在他最终得到的任何解决方案中接受一些限制。他将不得不进行试验并投入一些工作。还有this 需要考虑。

以上是关于捕获窗口的屏幕截图,不包括某些 HWND 的样式 (WS_SYSMENU)的主要内容,如果未能解决你的问题,请参考以下文章

BitBlt 屏幕截图在 Windows 10 上不起作用

如何获取 tkinter 窗口的屏幕截图

无法捕获窗口标题python

Linux常用截图软件

如何在不调整窗口大小的情况下使用 Java 在 Selenium Webdriver 中捕获屏幕截图 [重复]

截图出来是黑色的