Overview
Displaying the Target Window
SetForegroundWindow
procedure TForm1.Some_UIButtonMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
hnd : HWND;
DestRect : TRect; // and the variables are actually pointers
bm : TBitmap;
begin
DestRect.TopLeft := (Sender as TControl).ClientToScreen( point(x, y) );
hnd := WindowFromPoint(DestRect.TopLeft);
SetForegroundWindow(hnd); // This takes a "long" time
// other code here
// wait until the image has time to be displayed
Timer1.enabled := true;
while Timer1.enabled do // 100 ms is enough for test purposes
Application.ProcessMessages; // allow other tasks to run
BitBlt(bm.Canvas.Handle,0,0,bm.Width,bm.Height,
GetWindowDC(hnd),0,0,SRCCOPY);
SetForegroundWindow(form1.Handle); // restore the capture window
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.enabled := false; // just a flag
end;
|
Hide and Show
procedure TForm1.Some_UIButtonMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
hnd : HWND;
DestRect : TRect; // and the variables are actually pointers
bm : TBitmap;
begin
DestRect.TopLeft := (Sender as TControl).ClientToScreen( point(x, y) );
hnd := WindowFromPoint(DestRect.TopLeft);
// SetForegroundWindow(hnd); // not used when hide and show are used
// other code here
// wait until the image has time to be displayed
hide; // hide the capture form
Timer1.enabled := true;
while Timer1.enabled do // 100 ms is enough for thest purposes
Application.ProcessMessages; // allow other tasks to run
BitBlt(bm.Canvas.Handle,0,0,bm.Width,bm.Height,
GetWindowDC(hnd),0,0,SRCCOPY);
show; // put this form back on top
// SetForegroundWindow(form1.Handle); // not used when hide and show are used
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
Timer1.enabled := false; // just a flag
end;
|
As a result, I use this technique when a sub-window is used to capture graphs from a parent form, but not when capturing a random window in another application.
By the way, when the code is encapsulated in a component, self will no longer represent the form. In that case, the following modification is necessary.
GetParentForm(self).hide; // needed only if the desktop is captured GetParentForm(self).show; // make the form visible again |
Capture the Desktop
Capture the Image
procedure xxxx();
var
hnd : HWND;
SourceRect : TRect; // apparently, TRect is automatically allocated
DestRect : TRect; // and the variables are actually pointers
bm : TBitmap;
begin
// other code to get the handle and display the window here
GetWindowRect(hnd, SourceRect);
bm := TBitmap.Create;
try
bm.Width := SourceRect.Right - SourceRect.Left;
bm.Height := SourceRect.Bottom - SourceRect.Top ;
DestRect.Left := 0;
DestRect.Top := 0;
DestRect.Right := bm.Width;
DestRect.Bottom := bm.Height;
bm.Canvas.FillRect(DestRect); // probably not necessary
bm.Canvas.Lock;
try
BitBlt(bm.Canvas.Handle,0,0,bm.Width,bm.Height,
GetWindowDC(hnd),0,0,SRCCOPY);
finally
bm.Canvas.Unlock;
Image1.Picture.Bitmap.Assign(bm); // display the image
bm.Free;
end;
except
bm.Free;
raise;
end;
end;
|
The code I used actually does not capture a form window - it gets the window (control) that is under the mouse. As a result, it is able to capture a piece of a form. For some applications, that might be considered a bug. However, I like it because it makes it easier to document some of the controls I design. To get the whole form, release the mouse on a blank area .. or on the form window border.
Unfortunately, when a complete application window (form) is captured in Windows XP, whatever is behind the two upper corners is also captured. This is because those corners are rounded. (This is how I know that BitBlt is copying pixels from the screen and not from some internal context buffer.) Therefore, you need to place objects of the appropriate color behind the corners.
With transparent window borders (Vista and beyond), additional noise will be added to the image.
To provide a white background, you can open notepad and center that target window over that.
Author: Robert Clemenzi