This page is about how to print without using a report generator.
Writing directly to the printer's canvas provides more control than using someone else's components.
Unfortunately, Delphi 5 does not properly handle some functions. As a result, you must access the Windows API yourself.
The "Printers" Unit
A print job is started whenever any rendering is done through a Text variable or the printer’s canvas.
Canvas example
Printer.BeginDoc; Printer.Canvas.TextOut(0,0,'Place any text here'); Printer.EndDoc;
Simple text printing
procedure TForm1.Button1Click(Sender: TObject); var MyFile: TextFile; begin AssignPrn(MyFile); Rewrite(MyFile); Writeln(MyFile, 'Print this text'); System.CloseFile(MyFile); end;
This is from Printers.pas
{ AssignPrn - Assigns a Text variable to the currently selected printer. Any Write or Writeln's going to that file variable will be written on the printer using the Canvas property's font. A new page is automatically started if a CR is encountered on (or a Writeln is written to) the last line on the page. Closing the text file will imply a call to the Printer.EndDoc method. Note: only one Text variable can be open on the printer at a time. Opening a second will cause an exception.}
Drawing on the Canvas
As far as I can tell, all the canvas commands work ... except those that copy a TGraphic or a TCanvas. Basically, canvas methods based on the Windows StretchDraw method fail. (Of course, this is not documented anywhere in Delphi 5 since Windows XP was not written back then.)
The following Canvas commands fail when printing under Windows XP.
Images
Actually, with Delphi 5 and Windows 98, the provided documentation is pretty good. The problem is that with Windows 2000 and Windows XP, the technique used with Windows 98 no longer works. (Some references imply that this is a problem with all versions of Windows. However, my experience is that an *.exe file that worked perfectly with Windows 98 failed to print the images with Windows XP. Same printer, but different drivers - obviously.)
With Windows 98, you just copy the image canvas to the printer canvas. This example is from the Delphi 5 Printer function help.
with Printer do begin BeginDoc; Canvas.Draw((PageWidth - Bmp.Width ) div 2, (PageHeight - Bmp.Height) div 2, Bmp); EndDoc; end;However, starting with Windows 2000, that example no longer works. Now you need to use a device independent bitmap to accomplish this. This code fragment (based on code in Forms.pas) uses several Windows API calls.
var Info : PBitmapInfo; InfoSize : DWORD; Image : Pointer; ImageSize : DWORD; Bits : HBITMAP; begin Bits := bmp.Handle; // bmp is passed as a parameter GetDIBSizes(Bits, InfoSize, ImageSize); Info := AllocMem(InfoSize); try Image := AllocMem(ImageSize); try GetDIB(Bits, 0, Info^, Image^); StretchDIBits(Printer.Canvas.Handle, 5, 30, Bmp.Width, bmp.Height, 0, 0, bmp.Width, bmp.Height, Image, Info^, DIB_RGB_COLORS, SRCCOPY); finally FreeMem(Image, ImageSize); end; finally FreeMem(Info, InfoSize); end; end;
bmp.HandleType
Bmp := TBitmap.Create; bmp.HandleType := bmDIB ; // Device independent // Draw on bmp.canvas Printer.Canvas.StretchDraw( rect(0, bmp.Height, Bmp.Width, 0), bmp);It does NOT work.
Selecting a Printer
procedure TForm1.FormCreate(Sender: TObject); var i : integer; begin i := Printer.Printers.IndexOf('Brother PT-2500PC'); if i >= 0 then Printer.PrinterIndex := i; // set PaperSize - without this, // the Print and Print Setup dialog boxes produce access errors end;
Be sure to set the paper size ... even if you set it to a nonsense value. Using the Brother PT-2500PC barcode printer on a Windows 2000 system, just setting the printer produced access errors when I tried to open either the Print or Print Setup dialog boxes. Setting the paper size to an non-existent value ('11') fixed the problem.
In the final application, the printer name will be read from an ini file - never hard code strings.
Printer Properties
PageHeight PageWidth PageNumberThese are read/write
Copies OrientationThe Windows API provides several additional properties that Delphi does not make available, it also provides a way to write the readonly properties.
Normally, you won't need this ... but just in case.
var buffer1 : array[0..250] of char; buffer2 : array[0..250] of char; buffer3 : array[0..250] of char; ADevice, ADriver, APort : pchar; hDm : THandle; dm : DEVMODE; // For help, click on DEVMODE and press F1 Pdm : ^DEVMODE; s : string; i : integer; begin ADevice := buffer1; ADriver := buffer2; APort := buffer3; Printer.GetPrinter(ADevice, ADriver, APort, hDm); Pdm := GlobalLock (hDm); // get and set individual parameters here // just type "Pdm^." to get a list of available parameters // Some useful parameters include // PaperSize - Letter, Legal, A4 etc. // PaperLength, PaperWidth - values in millimeters // - use Printer.PageHeight & .PageWidth to read values in pixels // PrintQuality, YResolution - pixels per inch GlobalUnlock(hDm); printer.SetPrinter(ADevice, ADriver, APort, hDm); // maybe not necessary end;dmFields specifies which parameters are actually used.
In the above example, instead of
buffer1 : array[0..250] of char; ADevice : pchar; ADevice := buffer1;You could use
ADevice : array[0..250] of char;Do not use the following construct (I did), it assigns a copy of the structure to dm - when you set parameters, they never get back to the actual printer.
dm := Pdm^;
For alternate syntax, see Changing the papersize of a print job - by Borland Developer Support Staff. This reference also sets Printer.PrinterIndex to its current value before executing GetPrinter and after executing SetPrinter. I have no idea why (or if) that is necessary. (In one of the news groups, I was told that it fixed an obscure problem.)
Also see Macro to Obtain a List of Paper Names Supported by the Active Printer.
Selecting Paper Source (Paper Tray) shows how to modify parameters.
Selecting Paper Source - Errors
I've been working with an HP 4 Plus - a fairly common, but old, printer that reports 7 paper sources (bins) ... it actually only has 2 physical bins and 2 auto-select "bins" - the other 3 are options but not present.
The currently selected paper source is provided by
k := Pdm^.dmDefaultSource;Strings representing the available sources are retrieved via
cnt := DeviceCapabilities(ADevice, APort, DC_BINNAMES, buffer4, nil);The numbers associated with these is obtained via
cnt := DeviceCapabilities(Device, Port, DC_BINS , buffer, nil);This are the numbers (in order) that my system retrieves
15 1 1 4 2 5 11Presumably, one of these should match Pdm^.dmDefaultSource above. I'm sure that you already see a (the?) problem - 2 sources are associated with the number "1".
The default bin is 15 - Automatically Select
The problem is bin 257 - Auto Select (257 = 256 + 1 - It is actually the first "1")
The constants 1 through 15 are defined in windows.pas - device specific bins start at 256.
Selecting Paper Size - Errors
result := Pdm^.dmFormName;returns a 32 character string
cnt := DeviceCapabilities(ADevice, APort, DC_PAPERNAMES, buffer4, nil);returns a longer (64 character) string.
Several paper names are over 32 characters long.
Letter Extra Transverse 9\275 x 12 in 123456789 123456789 123456789 123456789Search windows.pas for "DMPAPER_".
Relevant definitions from windows.pas
PDeviceModeA = ^TDeviceModeA; PDeviceMode = PDeviceModeA; _devicemodeA = packed record // these are the fields described in the Windows API help file // for DEVMODE end; TDeviceMode = TDeviceModeA; TDeviceModeA = _devicemodeA; DEVMODEA = _devicemodeA; DEVMODE = DEVMODEA; PDevMode = PDeviceMode; {compatibility with Delphi 1.0} TDevMode = TDeviceMode; {compatibility with Delphi 1.0}In order to simplify design and debug, when given a choice, I prefer to use strings connected to the Delphi help system. When you click on DEVMODE and press F1, the related Windows API help is displayed. If you use TDeviceMode, nothing happens.
Interestingly, About Printing in the Windows SDK help file specifically states that BitBlt should be used to print bitmapped images.