2

I'm trying capture screenshot of determined minimized window from your handle, but this only capture all desktop window. I'm trying do like in this example of CodeProject website, but until now without sucess. So, how must I do for this works fine?

The I made until now >>

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: Complete code and updated and working fine, after help from friend @Remy Lebeau.

SAMPLE OF CAPTURE:

screenshot

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    You can't capture the screen of a minimized window, because there is nothing to capture. Windows only paints the visible part of the window, and a minimized window has no visible parts. – Ken White Nov 19 '14 at 18:48
  • 1
    @Ken That changed with Vista and Aero thumbs – David Heffernan Nov 19 '14 at 18:51
  • Possible duplicate: http://stackoverflow.com/q/21296989/2298252 – Günther the Beautiful Nov 19 '14 at 19:06
  • @Günther the Beautiful, could you give a example in Delphi, using SystemParametersInfo() function please? –  Nov 19 '14 at 19:11
  • @David: Through a standard DC obtained with `GetWindowDC`? For a full window image (and not a DWM thumbnail)? – Ken White Nov 19 '14 at 19:16
  • I don't know the details off hand. I don't know if you can only get a thumb. – David Heffernan Nov 19 '14 at 19:32
  • I swear I've seen this exact question asked months ago, for Delphi even... Can't find it now. – Jerry Dodge Nov 19 '14 at 19:37
  • 2
    @DavidHeffernan: "That changed with Vista and Aero thumbs" - are you sure about that? Because I've written thumbnail preview code for Vista+, and it is a PITA to capture previews of minimized windows, it does not work unless you restore them temporarily (and hide them so the user does not see them but Windows does). If you know another way to do it, please elaborate. – Remy Lebeau Nov 19 '14 at 20:01
  • @Remy Clearly apps paint themselves for Aero thumbs even when minimized. – David Heffernan Nov 19 '14 at 20:08
  • 2
    @DavisHeffernan: apps that want to provide previews for their own windows have to handle the `WM_DWMSENDICONICTHUMBNAIL` and `WM_DWMSENDICONICLIVEPREVIEWBITMAP` messages to provide bitmaps of the windows. They would simply redirect their paint handlers to draw the windows onto such bitmaps. But if you have a window you are not in control of, or simply cannot redirect its painting, then you don't have this option. MAYBE you can manually send the DWM messages to the minimized windows, but I have never tried that. – Remy Lebeau Nov 19 '14 at 20:22
  • @RemyLebeau, You have some in Delphi code example for capture a minimized window using SystemParametersInfo function like in your hint in another question answered in C++ above said by Günther the Beautiful like possible duplicated? –  Nov 19 '14 at 20:22
  • 1
    @Remy A plain vanilla Win32 app will have an aero preview when minimized, no? – David Heffernan Nov 19 '14 at 21:26

2 Answers2

4

Try something like this:

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I tried your code above, but without sucess :(. Where I can call my Screenshot procedure in [THIS](http://pastebin.com/wudGGtTy) case? Until now, nothing appears on Image1 component. –  Nov 20 '14 at 08:58
  • Thank you very much for help me! I have made some changes in you code above, because `GetLayeredWindowAttributes` undeclared :). After my update, still continues don't capturing the minimized window by handle :(. I updated my question above, see! –  Nov 20 '14 at 22:10
  • Your implementation and use of `GetLayeredWindowAttributes()` is wrong. First, the parameters are output parameters, so you need to declare them as pointers (or `var`) and get rid of the `Cardinal` typecasts. Second, The purpose of that section of code is to always call `SetLayeredWindowAttributes()` to give the window an alpha of 1 before showing it for capture, but you changed the code to call `SetLayeredWindowAttributes()` **and** `ShowWindow()` only if `LayeredWindowAttributes()` succeeds. Don't do that (and you are not doing correct error handling on `GetLayeredWindowAttributes()`). – Remy Lebeau Nov 20 '14 at 22:35
  • I took the `GetLayeredWindowAttributes()` out of my example. I don't use it in my own code anyway. – Remy Lebeau Nov 20 '14 at 22:37
  • still continues showing only full Desktop screen only like before :( –  Nov 20 '14 at 23:40
  • Are you sure you are using the correct HWND to begin with? Have you tried using `GetWindowDC(hWindow)` instead of `GetDC(0)`? Have you tried `PrintWindow()`? – Remy Lebeau Nov 21 '14 at 01:16
  • `PrintWindow()` worked fine. I updated the code above case someone need. Thank you very much for help me friend! –  Nov 26 '14 at 00:49
  • You don't need the `try/finally` around the `PrintWindow()` call, you should not be setting `Result` to true if `PrintWindow()` fails, and you are saving your Image to file if `WindowSnap()` fails. – Remy Lebeau Nov 26 '14 at 01:46
0

the code above works only the first time it is called for each window. If you call windowsnap twice for the same window handle it won't update the bitmap. Try to capture a minimized form with a label which changes every second ....