Как сделать скриншот активного окна в Delphi?

Для полных скриншотов я использую этот код:

form1.Hide;
sleep(500);
bmp := TBitmap.Create;
bmp.Height := Screen.Height;
bmp.Width := Screen.Width;
DCDesk := GetWindowDC(GetDesktopWindow);
BitBlt(bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, DCDesk, 0, 0, SRCCOPY);
form1.Show ;
FileName := 'Screenshot_'+FormatDateTime('mm-dd-yyyy-hhnnss',now());
bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
ReleaseDC(GetDesktopWindow, DCDesk);
bmp.Free;

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


person PuppyKevin    schedule 19.03.2009    source источник
comment
Вы должны опубликовать свои улучшенные версии здесь, под исходным примером, чтобы другие тоже могли извлечь пользу из вашего учебного курса.   -  person Daniel Rikowski    schedule 19.03.2009
comment
Пожалуйста, уточните: вы хотите сделать снимок экрана другой формы в той же программе или из любой программы, которая видна, когда форма Form1 скрыта?   -  person mghie    schedule 19.03.2009
comment
Я хочу, чтобы он брал из активного окна, когда форма скрыта.   -  person PuppyKevin    schedule 19.03.2009


Ответы (8)


  1. Прежде всего, вы должны получить правильное окно. Как уже отмечал острый зуб, вы должны использовать GetForegroundWindow вместо GetDesktopWindow. Вы все сделали правильно в своей улучшенной версии.
  2. Но тогда вам нужно изменить размер растрового изображения до фактического размера DC/Window. Вы еще этого не сделали.
  3. И затем убедитесь, что вы не захватываете какое-то полноэкранное окно!

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

Принимая во внимание описанные выше шаги, я успешно смог создать снимок экрана с одним окном с помощью вашего кода.

Подсказка: вы можете GetDC вместо GetWindowDC, если вас интересует только клиентская область. (Без границ окна)

EDIT: Вот что я сделал с вашим кодом:

Не используйте этот код! Посмотрите на улучшенную версию ниже.

procedure TForm1.Button1Click(Sender: TObject);
const
  FullWindow = True; // Set to false if you only want the client area.
var
  hWin: HWND;
  dc: HDC;
  bmp: TBitmap;
  FileName: string;
  r: TRect;
  w: Integer;
  h: Integer;
begin
  form1.Hide;
  sleep(500);
  hWin := GetForegroundWindow;

  if FullWindow then
  begin
    GetWindowRect(hWin,r);
    dc := GetWindowDC(hWin) ;
  end else
  begin
    Windows.GetClientRect(hWin, r);
    dc := GetDC(hWin) ;
  end;

  w := r.Right - r.Left;
  h := r.Bottom - r.Top;

  bmp := TBitmap.Create;
  bmp.Height := h;
  bmp.Width := w;
  BitBlt(bmp.Canvas.Handle, 0, 0, w, h, DC, 0, 0, SRCCOPY);
  form1.Show ;
  FileName := 'Screenshot_'+FormatDateTime('mm-dd-yyyy-hhnnss',now());
  bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
  ReleaseDC(hwin, DC);
  bmp.Free;
end;

РЕДАКТИРОВАТЬ 2: В соответствии с просьбой я добавляю улучшенную версию кода, но сохраняю старую в качестве ссылки. Вы должны серьезно подумать об использовании этого вместо исходного кода. В случае ошибок он будет вести себя намного лучше. (Ресурсы очищены, ваша форма снова будет видна, ...)

procedure TForm1.Button1Click(Sender: TObject);
const
  FullWindow = True; // Set to false if you only want the client area.
var
  Win: HWND;
  DC: HDC;
  Bmp: TBitmap;
  FileName: string;
  WinRect: TRect;
  Width: Integer;
  Height: Integer;
begin
  Form1.Hide;
  try
    Application.ProcessMessages; // Was Sleep(500);
    Win := GetForegroundWindow;

    if FullWindow then
    begin
      GetWindowRect(Win, WinRect);
      DC := GetWindowDC(Win);
    end else
    begin
      Windows.GetClientRect(Win, WinRect);
      DC := GetDC(Win);
    end;
    try
      Width := WinRect.Right - WinRect.Left;
      Height := WinRect.Bottom - WinRect.Top;

      Bmp := TBitmap.Create;
      try
        Bmp.Height := Height;
        Bmp.Width := Width;
        BitBlt(Bmp.Canvas.Handle, 0, 0, Width, Height, DC, 0, 0, SRCCOPY);
        FileName := 'Screenshot_' + 
          FormatDateTime('mm-dd-yyyy-hhnnss', Now());
        Bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
      finally
        Bmp.Free;
      end;
    finally
      ReleaseDC(Win, DC);
    end;
  finally
    Form1.Show;
  end;
end;
person Daniel Rikowski    schedule 19.03.2009
comment
Хорошо, это мой текущий код: pastebin.com/m43958302 Вот как получается картинка: i43.tinypic.com/xpcvw1.jpg Есть предложения? - person PuppyKevin; 19.03.2009
comment
Внимательнее надо быть :) 1. Вы меняете высоту и ширину на BitBlt. 2. Вы захватываете клиентскую область, но размер растрового изображения определяется по полной ширине. - person Daniel Rikowski; 19.03.2009
comment
ДР, не могли бы вы показать мне, что вы сделали из моего кода? Я думаю, что смогу учиться лучше, если увижу чужую работу. - person PuppyKevin; 19.03.2009
comment
@PuppyKevin: Сначала покажи нам, что ты приложил усилия. Ваш код на полпути, вам просто нужно сделать то, что вам сказал DR. И замените вызов Sleep() на Application.ProcessMessages(), чтобы позволить другим формам перерисовывать себя. - person mghie; 19.03.2009
comment
ДР, должен сказать, я снимаю перед тобой шляпу. Вы очень полезный человек :) Код, который вы мне дали, был очень близок к тому, что у меня было после некоторой доработки, я просто упустил несколько вещей. Кроме того, извините за мои навыки новичка, я все еще пытаюсь учиться. - person PuppyKevin; 20.03.2009
comment
@DR: Теперь, когда вы получили заслуженные голоса и приняли галочку, не могли бы вы пожалуйста сделать свой код действительно полезным для начинающих: используйте try и, наконец, избавляйтесь от ресурсов в порядке, обратном их получению. , и так далее? Спасибо. - person mghie; 20.03.2009
comment
@PuppyKevin: Нет проблем, я когда-то тоже был новичком, и мне тоже помогали другие, так что я просто рад помочь :) - person Daniel Rikowski; 20.03.2009
comment
@DR: Спасибо, теперь у вас есть еще один +1 ;-) - person mghie; 20.03.2009
comment
С помощью этого кода вы получите утечку дескриптора GDI. Запустите его 100 или 1000 раз и проверьте через ProcessExplorer. Количество дескрипторов GDI будет увеличиваться, пока не достигнет предела - person Alex Hide; 12.11.2018
comment
У вас есть идея, какая из ручек или какая ручка протекает? Возможно, я что-то упускаю, но код, кажется, закрывает все созданные им дескрипторы... - person Daniel Rikowski; 14.11.2018
comment
@DanielRikowski Я не уверен, почему это происходит. Я проверял ошибку, о которой мне сообщил один из пользователей, и обнаружил, что он создает дескриптор после использования bitblt, а не освобождает его. Я также был удивлен и сбит с толку тем, что в этом коде есть утечки дескрипторов, к сожалению, они есть (по крайней мере, в Delphi 10.2.3). Я решил это, добавив CreateCompatibleDC и CreateCompatibleBitmap, после этого утечки исчезли. - person Alex Hide; 14.11.2018

Ваш код может быть намного проще. Когда вы решили, какую форму вы хотите сохранить, попробуйте код, который я использую:

procedure SaveFormBitmapToBMPFile( AForm : TCustomForm; AFileName : string = '' );
// Copies this form's bitmap to the specified file
var
  Bitmap: TBitMap;
begin
  Bitmap := AForm.GetFormImage;
  try
    Bitmap.SaveToFile( AFileName );
  finally
    Bitmap.Free;
  end;
end;
person Brian Frost    schedule 19.03.2009
comment
Это будет работать только с формами, принадлежащими одному и тому же приложению. Но в таком случае это намного лучше, чем возиться с Windows API. - person Daniel Rikowski; 19.03.2009
comment
Очень красиво, но это только экономит клиентскую область формы - person Fr0sT; 06.07.2017

Это объединяет все подходы, описанные до сих пор. Он также обрабатывает сценарии с несколькими мониторами.

Передайте нужный снимок экрана и TJpegImage, и он назначит запрошенный снимок экрана этому изображению.

///////////
uses
  Jpeg;

type  //define an ENUM to describe the possible screenshot types.
  TScreenShotType = (sstActiveWindow, sstActiveClientArea,
    sstPrimaryMonitor, sstDesktop);
///////////

procedure TfrmMain.GetScreenShot(shotType: TScreenShotType;
  var img: TJpegImage);
var
  w,h: integer;
  DC: HDC;
  hWin: Cardinal;
  r: TRect;
  tmpBmp: TBitmap;
begin
  hWin := 0;
  case shotType of
    sstActiveWindow:
      begin
        //only the active window
        hWin := GetForegroundWindow;
        dc := GetWindowDC(hWin);
        GetWindowRect(hWin,r);
        w := r.Right - r.Left;
        h := r.Bottom - r.Top;
      end;  //sstActiveWindow
    sstActiveClientArea:
      begin
        //only the active client area (active window minus title bars)
        hWin := GetForegroundWindow;
        dc := GetDC(hWin);
        GetWindowRect(hWin,r);
        w := r.Right - r.Left;
        h := r.Bottom - r.Top;
      end;  //sstActiveClientArea
    sstPrimaryMonitor:
      begin
        //only the primary monitor.  If 1 monitor, same as sstDesktop.
        hWin := GetDesktopWindow;
        dc := GetDC(hWin);
        w := GetDeviceCaps(DC,HORZRES);
        h := GetDeviceCaps(DC,VERTRES);
      end;  //sstPrimaryMonitor
    sstDesktop:
      begin
        //ENTIRE desktop (all monitors)
        dc := GetDC(GetDesktopWindow);
        w := Screen.DesktopWidth;
        h := Screen.DesktopHeight;
      end;  //sstDesktop
    else begin
      Exit;
    end;  //case else
  end;  //case

  //convert to jpg
  tmpBmp := TBitmap.Create;
  try
    tmpBmp.Width := w;
    tmpBmp.Height := h;
    BitBlt(tmpBmp.Canvas.Handle,0,0,tmpBmp.Width,
      tmpBmp.Height,DC,0,0,SRCCOPY);
    img.Assign(tmpBmp);
  finally
    ReleaseDC(hWin,DC);
    FreeAndNil(tmpBmp);
  end;  //try-finally
end;
person JosephStyons    schedule 31.03.2009

JCL снова приходит на помощь..

    hwnd := GetForegroundWindow;
    Windows.GetClientRect(hwnd, r);
    JclGraphics.ScreenShot(theBitmap, 0, 0, r.Right - r.Left, r.Bottom - r.Top, hwnd);

    // use theBitmap...
person utku_karatas    schedule 19.03.2009

Никто здесь не опубликовал хороший ответ. Решение, которое было до сих пор предложено, это сделать снимок экрана, который «обрезается» в положении целевого окна. Что, если это окно находится за другим и в настоящее время не отображается операционной системой? Вот почему вам нужно использовать эту функцию. в виндовс ХР.

После быстрого поиска в Google вот пример кода: http://delphi.about.com/od/delphitips2008/qt/print_window.htm

person Community    schedule 31.03.2009
comment
+1 за более надежный ответ (обработка большего количества случаев и хорошая ссылка на отличный пример). Я действительно искал что-то подобное и нашел этот ответ лучшим. - person Erich Mirabal; 15.04.2009

Спасибо за это полезное представление. Я подумал, что могу превратить предложенный код в модуль для использования во всем моем приложении, вот код, который я запускаю на DX10.2 Tokyo. Обратите внимание на пример, следите за утечками памяти.

unit ScreenCapture;
interface

uses Windows, Vcl.Controls, Vcl.StdCtrls, VCL.Graphics,VCL.Imaging.JPEG, VCL.Forms;

function getScreenCapture(  FullWindow: Boolean = True ) : TBitmap;

implementation

function getScreenCapture( FullWindow: Boolean ) : TBitmap;
var
  Win: HWND;
  DC: HDC;

  WinRect: TRect;
  Width: Integer;
  Height: Integer;

begin
  Result := TBitmap.Create;

  //Application.ProcessMessages; // Was Sleep(500);
  Win := GetForegroundWindow;

  if FullWindow then
  begin
    GetWindowRect(Win, WinRect);
    DC := GetWindowDC(Win);
  end
    else
  begin
    Windows.GetClientRect(Win, WinRect);
    DC := GetDC(Win);
  end;
  try
    Width := WinRect.Right - WinRect.Left;
    Height := WinRect.Bottom - WinRect.Top;

    Result.Height := Height;
    Result.Width := Width;
    BitBlt(Result.Canvas.Handle, 0, 0, Width, Height, DC, 0, 0, SRCCOPY);
  finally
    ReleaseDC(Win, DC);
  end;
end;
end.

Пример :

//Any event or button click, screenCapture is a TBitmap
screenCapture := getScreenCapture();
try
  //Do some things with screen capture
  Image1.Picture.Graphic := screenCapture; 
finally 
  screenCapture.Free;
end;
person Andre Van Zuydam    schedule 04.05.2017

Используйте GetForegroundWindow() вместо GetDesktopWindow().

Вам нужно будет сохранить дескриптор, который возвращает GetForegroundWindow(), и передать сохраненное значение в ReleaseDC(), чтобы убедиться, что GetWindowDC() и ReleaseDC() вызываются точно для одного и того же окна, если активное окно изменяется между вызовами.

person sharptooth    schedule 19.03.2009
comment
Хорошо, теперь у меня есть это: pastebin.com/m2e334a4a Однако он по-прежнему занимает полноэкранный режим. - person PuppyKevin; 19.03.2009
comment
Проверьте значение дескриптора. Если он равен нулю, активного окна нет, и вы эффективно сбрасываете весь рабочий стол. - person sharptooth; 19.03.2009
comment
Я запутался. Каково значение ручки? Кроме того, как мне это проверить? - person PuppyKevin; 19.03.2009
comment
Надеюсь, у вас есть переменная, которой вы присваиваете результат GetForegroundWindow(). Вы можете добавить часы, чтобы увидеть фактическое значение этой переменной. - person sharptooth; 19.03.2009
comment
Вот вся моя процедура: pastebin.com/m711bc0c4 Нет, у меня нет переменной, которая результат GetForegroundWindow() - person PuppyKevin; 19.03.2009
comment
Вам нужна переменная для хранения значения, которое GetForegroundWindow() возвращает в любом случае. Как вы думаете, как будет работать ваш код, если активное окно изменится между вызовами GetWindowDC() и ReleaseDC()? - person sharptooth; 19.03.2009

Самая короткая версия кода Брайана Фроста:

Screen.ActiveForm.GetFormImage.SaveToFile(Screen.ActiveForm.Caption+'.bmp');

Всего одна строка кода (Скриншот активного окна в приложении MDI).

person Jan Kirchner    schedule 03.06.2012