如何正确地在Aero/DWM上截取特定窗口的屏幕截图

4
背景信息: 我编写并长期使用的MFC应用程序,基本上在用户按下Print Screen / Alt + Print Screen键时自动将屏幕截图保存到硬盘。直到最近几周我一直在使用Windows 7 RC,才开始使用与Aero相关的任何内容。

问题: 我正在使用标准的GetDC / BitBlt方法来捕获窗口内容。在进行常规全屏截取(无论打开了多少个窗口等)时,我对这种方法没有任何问题。但是当我尝试捕获前景窗口(Alt + PrintScreen)时,问题就出现了。以下是两个示例:

示例1 http://indiecodelabs.com/extern/example1.jpg 示例2 http://indiecodelabs.com/extern/example2.jpg 正如您所见,我在边框位置得到了垃圾数据。这在顶部更为明显,在两个截图中我们可以看到工具栏的重复。
我已经在谷歌上搜索了几个小时,但所有的文章都说在DWM下BitBlt/GetDC方法不起作用,但没有一个解释我们(开发者)应该怎么做才能在运行于DWM时保持相同的功能。任何帮助、指针、建议都将不胜感激。

非常抱歉,图像链接无法正常工作。我从未更新域名并忘记备份我的图片。 - enriquein
2个回答

3
BOOL CaptureWindow(const CString& filename)
{
    HWND hWnd = NULL;
    hWnd = ::GetForegroundWindow();   
    if(!hWnd)
    {
        return FALSE;
    }
    CRect rect;
    GetWindowRect(hWnd, &rect);
    rect.NormalizeRect();
    return DoCapture(CPoint(rect.left, rect.top), CSize(rect.Width(), rect.Height()), filename);
}

BOOL DoCapture(const POINT& coords, const SIZE& areaSize, const CString& filename)
{
    CDC dc;
    HDC hdc = GetDC(NULL);  // <-- We use this instead of GetWindowDC. 
                            // This is the only thing I had to change other than 
                            // getting the window coordinates in CaptureWindow()
    dc.Attach(hdc);

    // Create a memory DC into which the bitmap will be captured
    CDC memDC;
    memDC.CreateCompatibleDC(&dc);

    // If there is already a bitmap, delete it as we are going to replace it
    CBitmap bmp;
    bmp.DeleteObject();

    ICONINFO info;
    GetIconInfo((HICON)::GetCursor(), &info);   

    CURSORINFO cursor;
    cursor.cbSize = sizeof(CURSORINFO);
    GetCursorInfo(&cursor);

    bmp.CreateCompatibleBitmap(&dc, areaSize.cx, areaSize.cy);
    CBitmap * oldbm = memDC.SelectObject(&bmp);

    // Before we copy the image in, we blank the bitmap to
    // the background fill color
    memDC.FillSolidRect(&CRect(0,0,areaSize.cx, areaSize.cy), RGB(255,255,255));

    // Copy the window image from the window DC into the memory DC
    memDC.BitBlt(0, 0, areaSize.cx, areaSize.cy, &dc, coords.x, coords.y, SRCCOPY|CAPTUREBLT);

    // This part captures the mouse cursor and paints it on the image.
    if(programSettings.bWantCursor) 
    {    
        int osVersion = OSCheck::GetMajorOSVersion(); // For some reason cursor icons in 
                                                      // versions older than Vista are not
                                                      // top-aligned. So we compensate. 
        int offsetX = (osVersion >= 6) ? 0 : 10;
        int offsetY = (osVersion >= 6) ? 0 : 10;        

        CPoint cursorOffset(cursor.ptScreenPos.x - coords.x - offsetX, cursor.ptScreenPos.y - coords.y - offsetY);

        // Now draw the image of the cursor that we captured during
        // the mouse move. DrawIcon will draw a cursor as well.
        memDC.DrawIcon(cursorOffset, (HICON)cursor.hCursor);
    }
    memDC.SelectObject(oldbm);  

    Bitmap outputBitMap(bmp, NULL);

    // Optionally copy the image to the clipboard.
    if(programSettings.bWantClipboard)
    {
        if(OpenClipboard(NULL))
        {
            EmptyClipboard();
            SetClipboardData(CF_BITMAP, bmp);
            CloseClipboard();
        }
    }

    BOOL success = DumpImage(&outputBitMap, filename);

    DeleteObject(bmp.Detach());
    DeleteDC(dc.Detach());
    DeleteDC(memDC.Detach());
    return success;
}

参考资料:DumpImage基本上使用了Gdi::Bitmap的Save方法。由于它具有一些与示例无关的应用程序特定代码,因此已被省略。如果您想知道如何在屏幕截图中包含光标,则代码也在那里。希望它有所帮助。值得一提的是,与传统方法相反,这也将包括您可能在捕获窗口顶部的任何叠加窗口。此外,如果您使用此代码进行全屏截图,请注意它不会捕获视频游戏窗口。我实际上正在思考如何正确地执行此操作,而不必诉诸DirectX。这仅影响在Aero模式下运行的Windows Vista/7。


2

这是一个非常好的问题,但很遗憾我不知道确切的答案。我的第一个想法是抓取整个桌面并从中剪切出有趣的部分。

我查看了QT 4.5源代码,发现了类似于这样的内容。如果你将GetClientRect更改为GetWindowRect并去掉QT的样板代码,你就可以得到你想要的结果。不过看起来有点像黑客行为 :)


QPixmap QPixmap::grabWindow(WId winId, int x, int y, int w, int h )
{
    RECT r;
    GetClientRect(winId, &r);  
    if (w < 0) w = r.right - r.left;
    if (h < 0) h = r.bottom - r.top;  
    // Create and setup bitmap
    HDC display_dc = GetDC(0);
    HDC bitmap_dc = CreateCompatibleDC(display_dc);
    HBITMAP bitmap = CreateCompatibleBitmap(display_dc, w, h);
    HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);

    // copy data
    HDC window_dc = GetDC(winId);
    BitBlt(bitmap_dc, 0, 0, w, h, window_dc, x, y, SRCCOPY);

    // clean up all but bitmap
    ReleaseDC(winId, window_dc);
    SelectObject(bitmap_dc, null_bitmap);
    DeleteDC(bitmap_dc);

    QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap);

    DeleteObject(bitmap);
    ReleaseDC(0, display_dc);

    return pixmap;
}

这个可能有效,我要试一试。非常感谢,会跟进结果的。 - enriquein
很遗憾,根据情况来看,这种方法并不完全可行。由于某些原因,我错过了窗口顶部的一部分。有时我可以看到后面窗口的模糊,而其他时候我没有边框,还有时是黑色边框(桌面颜色设置为黑色)。我猜想这种方法很难做到完美。还是谢谢你的建议。 - enriquein
我的第一个想法是抓取整个桌面并从中剪切出有趣的部分。最终我终于尝试了这种方法,它非常有效。额外加分的是,这只涉及到更改现有代码库中的3行代码。 - enriquein

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