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