Сделать снимок экрана из выбранного свернутого окна

Я пытаюсь сделать снимок экрана определенного свернутого окна с вашего дескриптора, но это захватывает только все окно рабочего стола. Я пытаюсь сделать как в в этом примере веб-сайта CodeProject, но до сих пор безуспешно. Итак, что я должен сделать, чтобы это работало нормально?

То, что я сделал до сих пор >>

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 WIN64}StrToInt64{$ELSE}StrToInt{$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.

ОБРАЗЕЦ ЗАХВАТА:

скриншот


person Community    schedule 19.11.2014    source источник
comment
Вы не можете захватить экран свернутого окна, потому что нечего захватывать. Windows закрашивает только видимую часть окна, а свернутое окно не имеет видимых частей.   -  person Ken White    schedule 19.11.2014
comment
@Ken Это изменилось с большими пальцами Vista и Aero   -  person David Heffernan    schedule 19.11.2014
comment
Возможный дубликат: stackoverflow.com/q/21296989/2298252   -  person Günther the Beautiful    schedule 19.11.2014
comment
@ Гюнтер Прекрасный, не могли бы вы привести пример в Delphi с использованием функции SystemParametersInfo (), пожалуйста?   -  person    schedule 19.11.2014
comment
@David: Через стандартный контроллер домена, полученный с помощью GetWindowDC? Для полноэкранного изображения (а не миниатюры DWM)?   -  person Ken White    schedule 19.11.2014
comment
Я не знаю подробностей навскидку. Я не знаю, можешь ли ты получить только большой палец.   -  person David Heffernan    schedule 19.11.2014
comment
Клянусь, я видел этот точный вопрос, заданный несколько месяцев назад, даже для Delphi ... Сейчас не могу его найти.   -  person Jerry Dodge    schedule 19.11.2014
comment
@DavidHeffernan: Это изменилось с появлением Vista и Aero. Вы уверены в этом? Поскольку я написал код предварительного просмотра эскизов для Vista+, и это PITA для захвата предварительных просмотров свернутых окон, он не работает, если вы не восстановите их временно (и не скроете их, чтобы пользователь не видел их, но Windows видит). Если вы знаете другой способ сделать это, пожалуйста, уточните.   -  person Remy Lebeau    schedule 19.11.2014
comment
@Remy Очевидно, что приложения рисуют себя для больших пальцев Aero, даже когда они свернуты.   -  person David Heffernan    schedule 19.11.2014
comment
@DavisHeffernan: приложения, которые хотят предоставлять предварительный просмотр для своих окон, должны обрабатывать сообщения WM_DWMSENDICONICTHUMBNAIL и WM_DWMSENDICONICLIVEPREVIEWBITMAP для предоставления растровых изображений окон. Они просто перенаправляли свои обработчики рисования, чтобы рисовать окна на таких растровых изображениях. Но если у вас есть окно, которым вы не управляете, или просто не можете перенаправить его рисование, то у вас нет этой опции. ВОЗМОЖНО, вы можете вручную отправлять сообщения DWM в свернутые окна, но я никогда этого не пробовал.   -  person Remy Lebeau    schedule 19.11.2014
comment
@RemyLebeau, у вас есть пример кода Delphi для захвата свернутого окна с использованием функции SystemParametersInfo, как в вашей подсказке в другом вопросе, на который ответил на C ++ выше, сказанный Гюнтером Красивым, например, возможно, дублируется?   -  person    schedule 19.11.2014
comment
@Remy Простое ванильное приложение Win32 будет иметь предварительный просмотр в свернутом виде, не так ли?   -  person David Heffernan    schedule 20.11.2014


Ответы (2)


Попробуйте что-то вроде этого:

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 WIN64}StrToInt64{$ELSE}StrToInt{$ENDIF}(Edit1.Text));
  Bmp := TBitmap.Create;
  try
    if ScreenShot(hWd, bmp) then
      Image1.Picture.Assign(bmp);
  finally
    bmp.Free;
  end;
end;
person Remy Lebeau    schedule 19.11.2014
comment
Я попробовал ваш код выше, но безуспешно :(. Где я могу вызвать процедуру снимка экрана в ЭТОМ случае? Пока теперь ничего не появляется на компоненте Image1. - person ; 20.11.2014
comment
Большое спасибо за помощь мне! Я внес некоторые изменения в приведенный выше код, потому что GetLayeredWindowAttributes не объявлен :). После моего обновления по-прежнему не захватывает свернутое окно дескриптором :(. Я обновил свой вопрос выше, смотрите! - person ; 21.11.2014
comment
Ваша реализация и использование GetLayeredWindowAttributes() неверны. Во-первых, параметры являются выходными параметрами, поэтому вам нужно объявить их как указатели (или var) и избавиться от приведения типов Cardinal. Во-вторых, цель этого раздела кода состоит в том, чтобы всегда вызывать SetLayeredWindowAttributes(), чтобы дать окну альфа-канал, равный 1, прежде чем показывать его для захвата, но вы изменили код, чтобы вызывать SetLayeredWindowAttributes() и ShowWindow() только в случае успеха LayeredWindowAttributes(). Не делайте этого (и вы не выполняете правильную обработку ошибок на GetLayeredWindowAttributes()). - person Remy Lebeau; 21.11.2014
comment
Я убрал GetLayeredWindowAttributes() из своего примера. Я все равно не использую его в своем собственном коде. - person Remy Lebeau; 21.11.2014
comment
по-прежнему продолжает показывать только полный экран рабочего стола, как и раньше :( - person ; 21.11.2014
comment
Вы уверены, что используете правильный HWND для начала? Вы пробовали использовать GetWindowDC(hWindow) вместо GetDC(0)? Вы пробовали PrintWindow()? - person Remy Lebeau; 21.11.2014
comment
PrintWindow() работал нормально. Я обновил код выше, если кому-то нужно. Большое спасибо за помощь мне друг! - person ; 26.11.2014
comment
Вам не нужен try/finally вокруг вызова PrintWindow(), вы не должны устанавливать Result в true, если PrintWindow() терпит неудачу, и вы сохраняете свое изображение в файл, если WindowSnap() терпит неудачу. - person Remy Lebeau; 26.11.2014

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

person user1566931    schedule 15.08.2015