如何在Delphi中截取活动窗口的屏幕截图?

12

获取完整截图时,我使用以下代码:

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;

我该如何修改它以截取当前活动窗口的屏幕截图?


1
你应该在原始示例下面发布你的改进版本,这样其他人也可以从你的“学习历程”中受益。 - Daniel Rikowski
请澄清一下:您想在同一个程序中截取另一个窗体的屏幕截图,还是从Form1隐藏时可见的任何程序中截取? - mghie
我希望一旦表单隐藏后,它可以从活动窗口中获取。 - PuppyKevin
8个回答

21
  1. 首先,你必须得到正确的窗口。正如 sharptooth 已经指出的那样,你应该使用 GetForegroundWindow 而不是 GetDesktopWindow。在你的改进版中已经正确实现了这一点。
  2. 然后,你需要将位图调整为 DC/窗口的实际大小。你还没有做到这一点。
  3. 最后,请确保不要截取全屏窗口!

当我执行你的代码时,我的 Delphi IDE 被截取了,因为它默认是全屏的,造成了全屏截图的错觉。(尽管你的代码大部分是正确的)

考虑到以上步骤,我成功地使用你的代码创建了单个窗口的截图。

小提示:如果你只对客户端区域感兴趣(无窗口边框),可以使用 GetDC 而不是 GetWindowDC

编辑:这是我用你的代码做的:

你不应该使用这段代码!请查看下面的改进版本。

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;

@PuppyKevin:首先展示一下你的努力。你的代码已经完成了一半,你只需要按照DR告诉你的去做。并且用Application.ProcessMessages()替换Sleep()调用,让其他窗体重新绘制。 - mghie
@DR:既然你已经得到了应得的好评和接受的勾选标记,你能否让你的代码对初学者真正有帮助:使用try和finally,在资源释放时按照它们被获取的反向顺序进行处理等等?谢谢。 - mghie
@PuppyKevin:没问题,我也曾经是个初学者,也有其他人帮助过我,所以我很高兴能帮忙 :) - Daniel Rikowski
1
你知道哪个句柄或者是什么类型的句柄在泄漏吗?也许我漏看了什么,但是这段代码似乎关闭了它创建的所有句柄... - Daniel Rikowski
@DanielRikowski 我不确定为什么会发生这种情况。我正在检查用户向我报告的错误,并发现在使用bitblt后它创建了一个句柄,但没有释放它。我也感到惊讶和困惑,因为这段代码存在句柄泄漏问题,至少在Delphi 10.2.3上是如此。我通过添加CreateCompatibleDCCreateCompatibleBitmap来解决这个问题,泄漏问题得到了解决。 - Alex Hide
显示剩余9条评论

17

你的代码可以更简单。当你决定好要保存哪个表单时,可以尝试使用我使用的代码:

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;

3
这只适用于属于同一应用程序的表格。但在这种情况下,与调用 Windows API 相比,使用这种方法更好。 - Daniel Rikowski
1
非常好,但这只能保存表单的客户端区域。 - Fr0sT

9

这将结合到目前为止所描述的所有方法。它还处理多监视器方案。

传递您想要的屏幕截图类型和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;

6

JCL 再次挺身而出解决问题...

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

    // use theBitmap...

1
感谢您的有用提交,我想将提供的代码制作成一个单元,以便在我的应用程序中随处使用,这是我在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;

0

0

请使用GetForegroundWindow()代替GetDesktopWindow()。

您必须保存GetForegroundWindow()返回的句柄,并将保存的值传递到ReleaseDC()中-以确保在调用之间活动窗口更改的情况下,GetWindowDC()和ReleaseDC()准确地针对同一窗口调用。


好的,现在我有这个:http://pastebin.com/m2e334a4a但是它仍然全屏显示。 - PuppyKevin
检查句柄值是多少。如果它为空,就没有活动窗口,您实际上会将整个桌面强制退出。 - sharptooth
我很困惑。什么是句柄值?另外,我该如何检查它? - PuppyKevin
希望你有一个变量来赋值GetForegroundWindow()的结果。你可以添加一个监视器来查看该变量的实际值。 - sharptooth
这是我的整个过程:http://pastebin.com/m711bc0c4不,我没有一个变量保存GetForegroundWindow()的结果。 - PuppyKevin
你需要一个变量来存储GetForegroundWindow()返回的值。如果在GetWindowDC()和ReleaseDC()之间活动窗口发生变化,你认为你的代码会如何工作? - sharptooth

-3

Brian Frost 代码的最短版本:

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

只需一行代码(MDI应用程序中活动窗口的屏幕截图)。


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