Нечеловеческий PrintScreen

Воскресенье, Апрель 11th, 2010 | Программирование

1 звезда2 звезд3 звезд4 звезд5 звезд (7 голосов, средний: 4.43 из 5)
Loading ... Loading ...

Руками PrintScreen делается соответствующей кнопкой на клавиатуре – в результате имеем снимок всего экрана. Если же надо сделать снимок активного окна, то мы нажимаем <Alt>+<PrintScreen>.

Все просто, когда делаешь это руками. Однако сколько проблем возникает, если это надо сделать программно…

Как я уже сказал, проблема представлена в двух ипостасях:

  1. Снимок всего экрана;
  2. Снимок конкретного окна.

Рассмотрим первый вариант развития событий – снимок всего экрана.

Опять же, казалось бы, все просто, ан нет. Некоторые люди (и я в том числе) имеют несколько мониторов, т.е. рабочий стол растянут на 2 и более мониторов. Если делать снимок экрана вручную, то проблем не возникает – в скриншот попадают все мониторы, в случае же с программной реализацией, придется сделать снимок для каждого монитора, а затем склеить их воедино.

Вот небольшая функция возвращающая снимок экрана в качестве результата:

/// <summary>
/// Screent Shot
/// </summary>
/// <param name="currentScreen">Экран с которого делается снимок</param>
/// <returns>Снимок экрана</returns>
private Bitmap TakeScreenShot(Screen currentScreen)
{
    Bitmap bmpScreenShot = new Bitmap(currentScreen.Bounds.Width,
                                      currentScreen.Bounds.Height,
                                      PixelFormat.Format32bppArgb);
 
    Graphics gScreenShot = Graphics.FromImage(bmpScreenShot);
 
    gScreenShot.CopyFromScreen(currentScreen.Bounds.X,
                               currentScreen.Bounds.Y,
                               0, 0,
                               currentScreen.Bounds.Size,
                               CopyPixelOperation.SourceCopy);
    return bmpScreenShot;
}

В качестве параметра в функцию передается класс Screen, снимок которого она должна сделать, использовать данную функцию можно следующим образом:

// Перебираем все мониторы
foreach (Screen scr in Screen.AllScreens)
{
    Image img = TakeScreenShot(scr);
}
 
// Получить снимок главного монитора
Image pr = TakeScreenShot(Screen.PrimaryScreen);

Теперь рассмотрим другую проблему – создание снимка отдельного окна. Подводных камней тут еще больше.

Сделать снимок окна можно, только если его не перекрывает другое окно, т.к. снимок окна – снимок экрана только обрезанный по краям окна.

Решение проблемы перекрытия целевого окна каким-либо другим – сделать целевое окно текущим (фоновым/активным).

Данный способ не может решить абсолютно ВСЕ наши проблемы, т.к. перекрывающее окно может иметь статус «поверх всех окон». В таком случае необходимо сделать целевое окно также «поверх всех окон» таким образом наше окно будет выше всех окон, в том числе, и тех, что были «поверх всех окон».



К сожалению, это может продлиться не долго, т.к. другие окна могут узнать, что их «опустили» и снова выбиться вперед :) , но у нас будет достаточно времени, чтобы сделать скриншот целевого окна!

Итак, код класса, позволяющего делать скриншоты окон, если известен HWND целевого окна.

public class WindowImgaeCapture
{
    /// <summary>
    /// Получение снимка окна
    /// </summary>
    /// <param name="WindowHandle">HWND окна</param>
    /// <returns>Скриншот</returns>
    public static Image CaptureWindow(IntPtr WindowHandle)
    {
        if (WindowHandle != null)
        {
            User32.RECT windowRect = new User32.RECT();
            User32.GetWindowRect(WindowHandle, ref windowRect);
            int width = windowRect.right - windowRect.left + 1;
            int height = windowRect.bottom - windowRect.top + 1;
 
            User32.SetWindowPos(WindowHandle,
                                (System.IntPtr)User32.HWND_TOPMOST,
                                0, 0, 0, 0,
                                User32.SWP_NOMOVE |
                                User32.SWP_NOSIZE |
                                User32.SWP_FRAMECHANGED);
 
            IntPtr hdcSrc = User32.GetWindowDC(WindowHandle);
            IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
 
            IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,
                                                          width,
                                                          height);
 
            IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
 
            GDI32.BitBlt(hdcDest, 0, 0,
                         width, height,
                         hdcSrc, 0, 0,
                         GDI32.SRCCOPY);
 
            User32.SetWindowPos(WindowHandle,
                                (System.IntPtr)User32.HWND_NOTOPMOST,
                                0, 0, 0, 0,
                                User32.SWP_NOMOVE |
                                User32.SWP_NOSIZE |
                                User32.SWP_FRAMECHANGED);
 
            GDI32.SelectObject(hdcDest, hOld);
            GDI32.DeleteDC(hdcDest);
            User32.ReleaseDC(WindowHandle, hdcSrc);
            Image img = Image.FromHbitmap(hBitmap);
            GDI32.DeleteObject(hBitmap);
            return img;
        }
        else
            return null;
    }
 
    private class User32
    {
 
        public const int SWP_FRAMECHANGED = 0x0020;
        public const int SWP_NOMOVE = 0x0002;
        public const int SWP_NOSIZE = 0x0001;
        public const int SWP_NOZORDER = 0x0004;
        public const int HWND_TOPMOST = -1;
        public const int HWND_NOTOPMOST = -2;
 
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowDC(IntPtr hWnd);
 
        [DllImport("user32.dll")]
        public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
 
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowRect(IntPtr hWnd,
                                                  ref RECT rect);
 
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWindowPos(IntPtr hWnd,
                                               IntPtr hWndInsertAfter,
                                               int x, int y,
                                               int cx, int cy,
                                               uint uFlags);
    }
 
    private class GDI32
    {
 
        public const int SRCCOPY = 0x00CC0020;
 
        [DllImport("gdi32.dll")]
        public static extern bool BitBlt(IntPtr hObject,
            int nXDest, int nYDest,
            int nWidth, int nHeight, IntPtr hObjectSource,
            int nXSrc, int nYSrc, int dwRop);
 
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC,
                                                           int nWidth,
                                                           int nHeight);
 
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
 
        [DllImport("gdi32.dll")]
        public static extern bool DeleteDC(IntPtr hDC);
 
        [DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);
 
        [DllImport("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hDC,
                                                 IntPtr hObject);
    }
}

Пример использования данного класса.

Image img = WindowImageCapture.CaptureWindow(hwnd);

Хочу заметить, что статус «поверх всех окон» целевому окну выдается только на момент создания скриншота, а потом оно переходит в обычное состояние (к сожалению, если целевое окно уже имело статус «поверх всех окон», то оно станет обычным окном :( ).

«А где взять этот самый HWND окна?» – его можно получить при помощи функции FindWindow.

FindWindow – WinAPI функция, поэтому ее необходимо импортировать в программу.

Надеюсь, был полезен ;) .



8 коммент. на Нечеловеческий PrintScreen

twitter.com Yura Dyatlov
22.03.2011

А вот я когда вставил код(снимок всех экранов), жалуется на «Элемент «PixelFormat» не существует в текущем контексте.»
А его вообще нигде нет :(

twitter.com Yura Dyatlov
22.03.2011

С окном проблем нет, пока он не делает сам снимок, снимок выходит 1КБ и размером 1Х1 черный пикс…
код такой:
IntPtr hwnd = FindWindowByCaption((IntPtr)0,currentWindowName);
Image img = ScreenshotTaker.CaptureWindow(hwnd);
img.Save(Application.StartupPath+@»\asdas.jpg», ImageFormat.Jpeg);
Кажется в FindWindowByCaption, 0 не идет :) а что туда должно идти?

ZiV
22.03.2011

Вот описание PixelFormat, чтобы он стал доступным надо подключить System.Drawing.Imaging.

Вообще странно что FindWindowByCaption не работает. Что у тебя за ОСь? Если при вызове возникла ошибка, попробуй получить код последней ошибки через Marshal.GetLastWin32Error, вдруг поможет вот список возможных результатов вызова данной функции. А так вообще не рекомендую пользоваться FindWindowByCaption все-таки это алиас для FindWindow – используй его напрямую (так потом будет меньше вопросов к коду от других программистов).

twitter.com Yura Dyatlov
22.03.2011

Спасибо за ответ :)
Ось – семерка, х64…
Ошибка не вылазит… Просто пикс черный :)
Прочитав про FindWindowByCaption узнал что в параметр класса можно ставить НУЛЛ, и он тогда будет идти по наиболее подходящему окну…

ZiV
23.03.2011

FindWindowByCaption будучи псевдонимом FindWindow, имеет теже входные параметры. У FindWindow первый параметр – класс окна (строка), второй – заголовок окна (строка). В обычном режиме (если указаны оба параметра, и ниодин из них не равен NULL) FindWindow ищет окно заданного класса и с заданным заголовком окна. Если задан только один из параметров, а другой NULL, то ищется соответствие по заданному параметру. К сожалению, я не нашел ни слова о том, что будет если заданным параметрам удовлетворяет несколько окон – какое из них вернет функция?! В данном случае чтобы не надеяться на авось – получить все окна удовлетворяющие заданным требованиям, а потом самим решить какое из них подходит, для этого в WinAPI существует функция EnumWindows, как до нее добраться из C# можно найти здесь.

ZiV
23.03.2011

Визуальная ошибка не вылезет, или ты смотрел результат вызова функции GetLastWin32Error? Вот меня тут смущает «Win32″, может тебе стоит попробовать Win64, я не проверял, но вдруг есть такая функция.

Therefore
05.07.2011

Со скрином с монитора проблем не возникает.
А вот скриншот окна программы получается с черной рамкой по сторонам (да еще и больше обычного окна: 1033×747).
Вот например: http://img84.imageshack.us/img84/8586/59117341.jpg
Вроде и код копипастил, ничего не правил, но всё равно не получается.
В чем может быть дело, не знаете?

ZiV
06.07.2011

To Therefore, странно, в моих тестах все работало без проблем. Попробуйте, сделать приложение с пустой формой задайте ему фиксированную ширину и высоту (например, 800*600) и сделайте с него скриншот, а также выведете ширину и высоту, которая была получена через GetWindowRect (позиция левого верхнего и правого нижнего угла будет помещена в переменную windowRect). Ширина будет равна windowRect.right – windowRect.left, а высота windowRect.bottom – windowRect.top. Я это все к тому говорю, что в появлении рамки может быть виноваты либо неправильное расположение полей структуры User32.RECT и тогда вы получите ширину/высоту не соответствующую заданным вами размерам формы, либо виновата тема, которая уменьшает или частично по краям делает форму прозрачной.

Оставить отзыв

Сначала зарегистрируйтесь.

Поиск по блогу