从选定的最小化窗口捕获屏幕截图
Posted
技术标签:
【中文标题】从选定的最小化窗口捕获屏幕截图【英文标题】:Capture screenshot from selected minimized window 【发布时间】:2015-01-17 10:13:53 【问题描述】:我正在尝试从您的句柄中捕获确定的最小化窗口的屏幕截图,但这只会捕获所有桌面窗口。我正在尝试在 CodeProject 网站的this example 中做类似的事情,但直到现在还没有成功。那么,我该怎么做才能正常工作?
到现在为止我做的>>
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Winapi.DwmApi, System.Win.ComObj,
Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
Edit1: TEdit;
Label1: TLabel;
Button2: TButton;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
Private declarations
public
Public declarations
end;
var
Form1: TForm1;
implementation
$R *.dfm
function WindowSnap(hWindow: HWND; bmp: TBitmap): boolean;
var
user32DLLHandle: THandle;
printWindowAPI: function(sourceHandle: HWND; destinationHandle: HDC; nFlags: UINT): BOOL; stdcall;
R: TRect;
wp: WINDOWPLACEMENT;
ai: ANIMATIONINFO;
restoreAnimation: Boolean;
ExStyle: LONG_PTR;
begin
Result := False;
ExStyle := 0;
user32DLLHandle := GetModuleHandle(user32) ;
if user32DLLHandle <> 0 then
begin
@printWindowAPI := GetProcAddress(user32DLLHandle, 'PrintWindow') ;
if @printWindowAPI <> nil then
begin
if not IsWindow(hWindow) then Exit;
ZeroMemory(@wp, SizeOf(wp));
wp.length := SizeOf(wp);
GetWindowPlacement(hWindow, @wp);
ZeroMemory(@ai, SizeOf(ai));
restoreAnimation := False;
if wp.showCmd = SW_SHOWMINIMIZED then
begin
ai.cbSize := SizeOf(ai);
SystemParametersInfo(SPI_GETANIMATION, SizeOf(ai), @ai, 0);
if ai.iMinAnimate <> 0 then
begin
ai.iMinAnimate := 0;
SystemParametersInfo(SPI_SETANIMATION, SizeOf(ai), @ai, 0);
restoreAnimation := True;
end;
ExStyle := GetWindowLongPtr(hWindow, GWL_EXSTYLE);
if (ExStyle and WS_EX_LAYERED) <> WS_EX_LAYERED then begin
SetWindowLongPtr(hWindow, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
end;
SetLayeredWindowAttributes(hWindow, 0, 1, LWA_ALPHA);
ShowWindow(hWindow, SW_SHOWNOACTIVATE);
end;
GetWindowRect(hWindow, R) ;
bmp.Width := R.Right - R.Left;
bmp.Height := R.Bottom - R.Top;
bmp.Canvas.Lock;
try
Result := printWindowAPI(hWindow, bmp.Canvas.Handle, 0);
finally
bmp.Canvas.Unlock;
if (wp.showCmd = SW_SHOWMINIMIZED) then
begin
SetWindowPlacement(hWindow, @wp);
SetLayeredWindowAttributes(hWindow, 0, 255, LWA_ALPHA);
if (ExStyle and WS_EX_LAYERED) <> WS_EX_LAYERED then begin
SetWindowLongPtr(hWindow, GWL_EXSTYLE, ExStyle);
end;
if restoreAnimation then
begin
ai.iMinAnimate := 1;
SystemParametersInfo(SPI_SETANIMATION, SizeOf(ANIMATIONINFO), @ai, 0);
end;
end;
Result := True;
end;
end;
end;
end;
function FindHandleByTitle(WindowTitle: string): Hwnd;
var
NextHandle: Hwnd;
NextTitle: array[0..260] of char;
begin
NextHandle := GetWindow(Application.Handle, GW_HWNDFIRST);
while NextHandle > 0 do
begin
GetWindowText(NextHandle, NextTitle, 255);
if Pos(WindowTitle, StrPas(NextTitle)) <> 0 then
begin
Result := NextHandle;
Exit;
end
else
NextHandle := GetWindow(NextHandle, GW_HWNDNEXT);
end;
Result := 0;
end;
function EnumWindowsProc(wHandle: HWND; lb: TListBox): Bool; stdcall; export;
var
Title, ClassName: array[0..255] of char;
begin
Result := True;
GetWindowText(wHandle, Title, 255);
GetClassName(wHandle, ClassName, 255);
if IsWindowVisible(wHandle) then
lb.Items.Add('Title: '+string(Title) + ' - Class: ' + string(ClassName) + ' - Handle: ' + IntToStr(FindHandleByTitle(Title)));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
EnumWindows(@EnumWindowsProc, Integer(Listbox1));
end;
procedure TForm1.Button2Click(Sender: TObject);
var
hWd: HWND;
Bmp: TBitmap;
begin
hWd := HWND($IFDEF WIN64StrToInt64$ELSEStrToInt$ENDIF(Edit1.Text));
Bmp := TBitmap.Create;
try
if WindowSnap(hWd, bmp) then
Image1.Picture.Assign(bmp);
Image1.Refresh;
Image1.Picture.SaveToFile('c:\screen.bmp');
finally
bmp.Free;
end;
end;
end.
PS:在朋友@Remy Lebeau 的帮助下,完整的代码、更新和工作正常。
捕获样本:
【问题讨论】:
您无法捕获最小化窗口的屏幕,因为没有可捕获的内容。 Windows 只绘制窗口的可见部分,最小化的窗口没有可见部分。 @Ken 这改变了 Vista 和 Aero 拇指 @DavidHeffernan:“Vista 和 Aero 大拇指改变了这种情况”——你确定吗?因为我已经为 Vista+ 编写了缩略图预览代码,并且它是一个 PITA 来捕获最小化窗口的预览,所以除非您暂时恢复它们(并隐藏它们以便用户看不到它们,但 Windows 可以),否则它不起作用。如果您知道另一种方法,请详细说明。 @DavisHeffernan:想要为自己的窗口提供预览的应用程序必须处理WM_DWMSENDICONICTHUMBNAIL
和WM_DWMSENDICONICLIVEPREVIEWBITMAP
消息以提供窗口的位图。他们只需重定向他们的绘图处理程序以将窗口绘制到此类位图上。但是如果你有一个你无法控制的窗口,或者根本无法重定向它的绘画,那么你就没有这个选项。也许您可以手动将 DWM 消息发送到最小化的窗口,但我从未尝试过。
@Remy 一个普通的 Win32 应用程序在最小化时会有一个航空预览,不是吗?
【参考方案1】:
试试这样的:
function ScreenShot(hWindow: HWND; bm: TBitmap): Boolean;
var
R: TRect;
ScreenDc: HDC;
lpPal: PLOGPALETTE;
wp: WINDOWPLACEMENT;
ai: ANIMATIONINFO;
hWd: HWND;
restoreAnimation: Boolean;
ExStyle: LONG_PTR;
begin
Result := False;
if not IsWindow(hWindow) then Exit;
ZeroMemory(@wp, SizeOf(wp));
wp.length := SizeOf(wp);
GetWindowPlacement(hWindow, @wp);
ZeroMemory(@ai, SizeOf(ai));
restoreAnimation := False;
if wp.showCmd = SW_SHOWMINIMIZED then
begin
ai.cbSize := SizeOf(ai);
SystemParametersInfo(SPI_GETANIMATION, SizeOf(ai), @ai, 0);
if ai.iMinAnimate <> 0 then
begin
ai.iMinAnimate := 0;
SystemParametersInfo(SPI_SETANIMATION, SizeOf(ai), @ai, 0);
restoreAnimation := True;
end;
ExStyle := GetWindowLongPtr(hWindow, GWL_EXSTYLE);
if (ExStyle and WS_EX_LAYERED) <> WS_EX_LAYERED then begin
SetWindowLongPtr(hWindow, GWL_EXSTYLE, ExStyle or WS_EX_LAYERED);
end;
SetLayeredWindowAttributes(hWindow, 0, 1, LWA_ALPHA);
ShowWindow(hWindow, SW_SHOWNOACTIVATE);
end;
GetWindowRect(hWindow, R);
bm.Width := R.Right - R.Left;
bm.Height := R.Bottom - R.Top;
ScreenDc := GetDC(0);
if (GetDeviceCaps(ScreenDc, RASTERCAPS) and RC_PALETTE) = RC_PALETTE then
begin
GetMem(lpPal, SizeOf(TLOGPALETTE) + (255 * SizeOf(TPALETTEENTRY)));
ZeroMemory(lpPal, SizeOf(TLOGPALETTE) + (255 * SizeOf(TPALETTEENTRY)));
lpPal^.palVersion := $300;
lpPal^.palNumEntries := GetSystemPaletteEntries(ScreenDc, 0, 256, lpPal^.palPalEntry);
if lpPal^.PalNumEntries <> 0 then begin
bm.Palette := CreatePalette(lpPal^);
end;
FreeMem(lpPal, SizeOf(TLOGPALETTE) + (255 * SizeOf(TPALETTEENTRY)));
end;
BitBlt(bm.Canvas.Handle, 0, 0, bm.Width, bm.Height, ScreenDc, R.Left, R.Top, SRCCOPY);
ReleaseDc(0, ScreenDc);
if (wp.showCmd = SW_SHOWMINIMIZED) then
begin
SetWindowPlacement(hWindow, @wp);
SetLayeredWindowAttributes(hWindow, 0, 255, LWA_ALPHA);
if (ExStyle and WS_EX_LAYERED) <> WS_EX_LAYERED then begin
SetWindowLongPtr(hWindow, GWL_EXSTYLE, ExStyle);
end;
if restoreAnimation then
begin
ai.iMinAnimate := 1;
SystemParametersInfo(SPI_SETANIMATION, SizeOf(ANIMATIONINFO), @ai, 0);
end;
end;
Result := True;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
hWd: HWND;
Bmp: TBitmap;
begin
hWd := HWND($IFDEF WIN64StrToInt64$ELSEStrToInt$ENDIF(Edit1.Text));
Bmp := TBitmap.Create;
try
if ScreenShot(hWd, bmp) then
Image1.Picture.Assign(bmp);
finally
bmp.Free;
end;
end;
【讨论】:
我在上面尝试了您的代码,但没有成功:(。在THIS 案例中我可以在哪里调用我的屏幕截图过程?到目前为止,Image1 组件上没有任何内容。 非常感谢您对我的帮助!我在上面的代码中做了一些更改,因为GetLayeredWindowAttributes
未声明 :)。更新后,仍然继续不通过句柄捕获最小化窗口:(。我在上面更新了我的问题,请参阅!
你对GetLayeredWindowAttributes()
的实现和使用是错误的。首先,参数是输出参数,因此您需要将它们声明为指针(或var
)并摆脱Cardinal
类型转换。其次,该部分代码的目的是始终调用 SetLayeredWindowAttributes()
以在显示窗口以进行捕获之前为窗口提供 1 的 alpha,但是您将代码更改为调用 SetLayeredWindowAttributes()
和 @987654329 @ 仅当 LayeredWindowAttributes()
成功时。不要那样做(而且你没有对GetLayeredWindowAttributes()
进行正确的错误处理)。
我把GetLayeredWindowAttributes()
去掉了我的例子。反正我不会在我自己的代码中使用它。
仍然像以前一样只显示完整的桌面屏幕:(【参考方案2】:
上面的代码只在每个窗口第一次被调用时才有效。 如果您为同一个窗口句柄调用 windowsnap 两次,它不会更新位图。 尝试捕获具有每秒更改的标签的最小化表单....
【讨论】:
以上是关于从选定的最小化窗口捕获屏幕截图的主要内容,如果未能解决你的问题,请参考以下文章
捕获窗口的屏幕截图,不包括某些 HWND 的样式 (WS_SYSMENU)
如何使用Selenium Webdriver捕获特定元素而不是整个页面的屏幕截图?