detempel
asked on
Print a Window
How can I print the contents of a specific 'child-window' on a printer. I'm looking for something like:
Print(WHandle)
Print(WHandle)
You can use Print method of any Form. Actually it uses GetFormImage: TBitmap and draws that to the printer HDC.
ASKER
I see my question is not complete. I'm referring to a window outside the Delphi-environment/program . I want to print a (child)-window from ANY application using Delphi.
With Delphi I can look for the right WindowHandle by scanning for text in the title in all ChildWindows from the TopWindow of the application in question.
With Delphi I can look for the right WindowHandle by scanning for text in the title in all ChildWindows from the TopWindow of the application in question.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
LukA_YJK,
WM_PRINT won't always work. Some system windows will do OK most of the time, but window of arbitrary application doesn't tipically handle WM_PRINT event for the owner draw area.
Therefore, using WM_PRINT will often lead to 'half-printed' windows, where system drawn elements (like caption bar, menu, etc...) are printed, but application drawn elements are not.
detempel,
I'm afraid you must bring window of interest in the foreground to be able to capture it's bitmap and print it. I pasted together a set of more or less well-known procedures from different NGs and FAQs, and used them in a demo. Copy/paste them and see if it works for you.
Flicker will be unavoidable, due to the pulling/pushing window to/from foreground. Also, window is streched to fit the size of the paper. Adjust this as you see fit.
function GetWindowBitmap(hWindow:hW nd):HBitma p;
var DC,MemDC:HDC;
bmWidth,bmHeight:Integer;
TR:tRect;
bm,obm:HBitmap;
begin
// full window bitmap
GetWindowRect(hWindow,TR);
{ note the +1 }
bmWidth:=TR.Right-TR.Left+ 1;
bmHeight:=TR.Bottom-TR.Top +1;
// Window DC
DC:=GetDC(GetDesktopWindow );
MemDC:=CreateCompatibleDC( DC);
bm:=CreateCompatibleBitmap (DC,bmWidt h,bmHeight );
// now select the bitmap to memDC
obm:=SelectObject(MemDC,bm );
// and copy the window
BitBlt(MemDC,0,0,bmWidth,b mHeight, DC, TR.Left, TR.Top,SRCCOPY);
// clean up
SelectObject(MemDC,obm);
DeleteDC(MemDC);
ReleaseDC(GetDesktopWindow , DC);
// here is your bitmap
GetWindowBitmap:=bm;
end;
procedure DrawImage(Canvas: TCanvas; DestRect: TRect; ABitmap:
TBitmap);
var
Header, Bits: Pointer;
HeaderSize: DWORD;
BitsSize: DWORD;
begin
GetDIBSizes(ABitmap.Handle , HeaderSize, BitsSize);
Header := AllocMem(HeaderSize);
Bits := AllocMem(BitsSize);
try
GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);
StretchDIBits(Canvas.Handl e, DestRect.Left, DestRect.Top,
DestRect.Right, DestRect.Bottom,
0, 0, ABitmap.Width, ABitmap.Height, Bits,TBitmapInfo(Header^),
DIB_RGB_COLORS, SRCCOPY);
finally
FreeMem(Header, HeaderSize);
FreeMem(Bits, BitsSize);
end;
end;
{ Print a Bitmap using the whole Printerpage }
procedure PrintBitmap(ABitmap: TBitmap);
var
relheight, relwidth: integer;
begin
screen.cursor := crHourglass;
Printer.BeginDoc;
if ((ABitmap.width / ABitmap.height) > (printer.pagewidth /printer.pageheight)) then
begin
{ Stretch Bitmap to width of Printerpage }
relwidth := printer.pagewidth;
relheight := MulDiv(ABitmap.height, printer.pagewidth,ABitmap. width);
end else
begin
{ Stretch Bitmap to height of Printerpage }
relwidth := MulDiv(ABitmap.width, printer.pageheight, ABitmap.height);
relheight := printer.pageheight;
end;
DrawImage(Printer.Canvas, Rect(0, 0, relWidth, relHeight), ABitmap);
Printer.EndDoc;
screen.cursor := crDefault;
end;
procedure PrintWindow(WinHandle: HWnd);
var currentWin: HWnd;
b: TBitmap;
begin
b:=TBitmap.Create;
currentWin:=GetForegroundW indow;
SetForegroundWindow(WinHan dle);
while GetForegroundWindow<>WinHa ndle do Sleep(0);
b.Handle:=GetWindowBitmap( WinHandle) ;
SetForegroundWindow(curren tWin);
PrintBitmap(b);
b.Free;
end;
// DEMO:
//-----------
procedure TForm1.Button1Click(Sender : TObject);
begin // Prints the notepad window
if FindWindow('Notepad',nil)< >0 then
PrintWindow(FindWindow('No tepad',nil ))
else ShowMessage('Notepad window not found!');
end;
WM_PRINT won't always work. Some system windows will do OK most of the time, but window of arbitrary application doesn't tipically handle WM_PRINT event for the owner draw area.
Therefore, using WM_PRINT will often lead to 'half-printed' windows, where system drawn elements (like caption bar, menu, etc...) are printed, but application drawn elements are not.
detempel,
I'm afraid you must bring window of interest in the foreground to be able to capture it's bitmap and print it. I pasted together a set of more or less well-known procedures from different NGs and FAQs, and used them in a demo. Copy/paste them and see if it works for you.
Flicker will be unavoidable, due to the pulling/pushing window to/from foreground. Also, window is streched to fit the size of the paper. Adjust this as you see fit.
function GetWindowBitmap(hWindow:hW
var DC,MemDC:HDC;
bmWidth,bmHeight:Integer;
TR:tRect;
bm,obm:HBitmap;
begin
// full window bitmap
GetWindowRect(hWindow,TR);
{ note the +1 }
bmWidth:=TR.Right-TR.Left+
bmHeight:=TR.Bottom-TR.Top
// Window DC
DC:=GetDC(GetDesktopWindow
MemDC:=CreateCompatibleDC(
bm:=CreateCompatibleBitmap
// now select the bitmap to memDC
obm:=SelectObject(MemDC,bm
// and copy the window
BitBlt(MemDC,0,0,bmWidth,b
// clean up
SelectObject(MemDC,obm);
DeleteDC(MemDC);
ReleaseDC(GetDesktopWindow
// here is your bitmap
GetWindowBitmap:=bm;
end;
procedure DrawImage(Canvas: TCanvas; DestRect: TRect; ABitmap:
TBitmap);
var
Header, Bits: Pointer;
HeaderSize: DWORD;
BitsSize: DWORD;
begin
GetDIBSizes(ABitmap.Handle
Header := AllocMem(HeaderSize);
Bits := AllocMem(BitsSize);
try
GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^);
StretchDIBits(Canvas.Handl
DestRect.Right, DestRect.Bottom,
0, 0, ABitmap.Width, ABitmap.Height, Bits,TBitmapInfo(Header^),
DIB_RGB_COLORS, SRCCOPY);
finally
FreeMem(Header, HeaderSize);
FreeMem(Bits, BitsSize);
end;
end;
{ Print a Bitmap using the whole Printerpage }
procedure PrintBitmap(ABitmap: TBitmap);
var
relheight, relwidth: integer;
begin
screen.cursor := crHourglass;
Printer.BeginDoc;
if ((ABitmap.width / ABitmap.height) > (printer.pagewidth /printer.pageheight)) then
begin
{ Stretch Bitmap to width of Printerpage }
relwidth := printer.pagewidth;
relheight := MulDiv(ABitmap.height, printer.pagewidth,ABitmap.
end else
begin
{ Stretch Bitmap to height of Printerpage }
relwidth := MulDiv(ABitmap.width, printer.pageheight, ABitmap.height);
relheight := printer.pageheight;
end;
DrawImage(Printer.Canvas, Rect(0, 0, relWidth, relHeight), ABitmap);
Printer.EndDoc;
screen.cursor := crDefault;
end;
procedure PrintWindow(WinHandle: HWnd);
var currentWin: HWnd;
b: TBitmap;
begin
b:=TBitmap.Create;
currentWin:=GetForegroundW
SetForegroundWindow(WinHan
while GetForegroundWindow<>WinHa
b.Handle:=GetWindowBitmap(
SetForegroundWindow(curren
PrintBitmap(b);
b.Free;
end;
// DEMO:
//-----------
procedure TForm1.Button1Click(Sender
begin // Prints the notepad window
if FindWindow('Notepad',nil)<
PrintWindow(FindWindow('No
else ShowMessage('Notepad window not found!');
end;
Cynna, thanks for interesting fact about WM_PRINT. But maybe my first comment is not a bad idea. We can Use the Source ;) in Forms.pas Actually the implementation of GetFormImage is different in different versions. But I liked Delphi 2.0 version, seems it is TForm independent. Let me quote the following piece of code:
function TForm.GetFormImage: TBitmap;
var
ScreenDC, PrintDC: HDC;
OldBits, PrintBits: HBITMAP;
PaintLParam: Longint;
procedure PrintHandle(Handle: HWND);
var
R: TRect;
Child: HWND;
SavedIndex: Integer;
begin
if IsWindowVisible(Handle) then
begin
SavedIndex := SaveDC(PrintDC);
Windows.GetClientRect(Hand le, R);
MapWindowPoints(Handle, Self.Handle, R, 2);
with R do
begin
SetWindowOrgEx(PrintDC, -Left, -Top, nil);
IntersectClipRect(PrintDC, 0, 0, Right - Left, Bottom - Top);
end;
SendMessage(Handle, WM_ERASEBKGND, PrintDC, 0);
SendMessage(Handle, WM_PAINT, PrintDC, PaintLParam);
Child := GetWindow(Handle, GW_CHILD);
if Child <> 0 then
begin
Child := GetWindow(Child, GW_HWNDLAST);
while Child <> 0 do
begin
PrintHandle(Child);
Child := GetWindow(Child, GW_HWNDPREV);
end;
end;
RestoreDC(PrintDC, SavedIndex);
end;
end;
begin
Result := nil;
ScreenDC := GetDC(0);
PaintLParam := 0;
try
PrintDC := CreateCompatibleDC(ScreenD C);
try
PrintBits := CreateCompatibleBitmap(Scr eenDC, ClientWidth, ClientHeight);
try
OldBits := SelectObject(PrintDC, PrintBits);
try
{ Clear the contents of the bitmap }
FillRect(PrintDC, ClientRect, Brush.Handle);
{ Paint form into a bitmap }
PrintHandle(Handle);
finally
SelectObject(PrintDC, OldBits);
end;
Result := TBitmap.Create;
Result.Handle := PrintBits;
PrintBits := 0;
except
Result.Free;
if PrintBits <> 0 then DeleteObject(PrintBits);
raise;
end;
finally
DeleteDC(PrintDC);
end;
finally
ReleaseDC(0, ScreenDC);
end;
end;
Maybe we should omit coordinate mapping and printing Child windows. It seems the Force is strong with detempel, so it will not be a problem to print obtained bitmap... Hope it will be helpfull...
function TForm.GetFormImage: TBitmap;
var
ScreenDC, PrintDC: HDC;
OldBits, PrintBits: HBITMAP;
PaintLParam: Longint;
procedure PrintHandle(Handle: HWND);
var
R: TRect;
Child: HWND;
SavedIndex: Integer;
begin
if IsWindowVisible(Handle) then
begin
SavedIndex := SaveDC(PrintDC);
Windows.GetClientRect(Hand
MapWindowPoints(Handle, Self.Handle, R, 2);
with R do
begin
SetWindowOrgEx(PrintDC, -Left, -Top, nil);
IntersectClipRect(PrintDC,
end;
SendMessage(Handle, WM_ERASEBKGND, PrintDC, 0);
SendMessage(Handle, WM_PAINT, PrintDC, PaintLParam);
Child := GetWindow(Handle, GW_CHILD);
if Child <> 0 then
begin
Child := GetWindow(Child, GW_HWNDLAST);
while Child <> 0 do
begin
PrintHandle(Child);
Child := GetWindow(Child, GW_HWNDPREV);
end;
end;
RestoreDC(PrintDC, SavedIndex);
end;
end;
begin
Result := nil;
ScreenDC := GetDC(0);
PaintLParam := 0;
try
PrintDC := CreateCompatibleDC(ScreenD
try
PrintBits := CreateCompatibleBitmap(Scr
try
OldBits := SelectObject(PrintDC, PrintBits);
try
{ Clear the contents of the bitmap }
FillRect(PrintDC, ClientRect, Brush.Handle);
{ Paint form into a bitmap }
PrintHandle(Handle);
finally
SelectObject(PrintDC, OldBits);
end;
Result := TBitmap.Create;
Result.Handle := PrintBits;
PrintBits := 0;
except
Result.Free;
if PrintBits <> 0 then DeleteObject(PrintBits);
raise;
end;
finally
DeleteDC(PrintDC);
end;
finally
ReleaseDC(0, ScreenDC);
end;
end;
Maybe we should omit coordinate mapping and printing Child windows. It seems the Force is strong with detempel, so it will not be a problem to print obtained bitmap... Hope it will be helpfull...
ASKER
Thanks for your efforts in helping me. For the moment I won't have to solve this problem so I want to give my points at this moment and close the question.
Erik.
Erik.