Как правильно сделать скриншот определенного окна в Aero / DWM - PullRequest
4 голосов
/ 19 июля 2009

Справочная информация: У меня есть это приложение MFC, которое я кодировал и долгое время использовал, которое в значительной степени автоматически сохраняет скриншоты на жесткий диск, когда пользователь нажимает клавишу Print Screen / Alt + Print Screen. Я откладывал использование чего-либо, связанного с Aero, до сих пор, поскольку я использую Windows 7 RC в течение нескольких недель.

Проблема: Я использую стандартный метод GetDC / BitBlt для захвата содержимого окна. У меня нет проблем с этим методом при выполнении регулярных полноэкранных захватов (независимо от того, сколько окон открыто и т.д.). Проблема возникает, когда я пытаюсь захватить окно переднего плана (Alt + PrintScreen). Вот два примера:

Пример 1 http://indiecodelabs.com/extern/example1.jpg

Пример 2 http://indiecodelabs.com/extern/example2.jpg

Как видите, я получаю мусор там, где должны быть границы. Это более заметно в верхней части, где мы видим некоторое дублирование панели инструментов на обоих снимках экрана.

Я уже несколько часов гуглю по этому поводу, и все, что я могу найти, это статьи о том, что в DWM метод BitBtl / GetDC не будет работать, но не могу найти ни одного, объясняющего, что мы (разработчики) должны делать чтобы поддерживать ту же функциональность в наших приложениях при работе в DWM.

Любая помощь, указатели, предложения будут с благодарностью.

Ответы [ 2 ]

3 голосов
/ 16 декабря 2009
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 в значительной степени использует метод Save Gdi :: Bitmap. Он был опущен, потому что у него есть некоторый специфичный для приложения код, который не имеет отношения к примеру. Также дополнительным бонусом является то, что если вам интересно, как включить курсор в скриншот, то код также там. Надеюсь, поможет. Также стоит упомянуть, что в отличие от традиционного подхода, это также будет включать любые наложенные окна, которые вы можете иметь поверх захваченного окна. Кроме того, если вы используете этот код для полноэкранного захвата, имейте в виду, что он не будет захватывать окна видеоигр. Мне действительно интересно, как сделать это правильно, не прибегая к DirectX. Это влияет только на Windows Vista / 7 при работе в режиме Aero.

2 голосов
/ 19 июля 2009

Это отличный вопрос, на который, к сожалению, я не знаю точного ответа. Моей первой идеей было собрать весь рабочий стол и вырезать из него интересную часть.

Я копался в источниках 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 &lt 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;
}
...