Printing in Delphi

Print

Link

You can send a copy of a form as a bitmap to a printer by simply calling the print procedure. One way to do this might be to place a button a form and add a simple procedure:

procedure TConsignmentDetails.Button2Click(Sender: TObject);
begin
  print;
end;

This could be useful for debugging during the development of a program.

Some controls have their own Print method - the Memo does not have a Print Method but the RichEdit control does. We can thus write something like this to print the contents of a RichEdit control:

for i:= 0 to richedit1.Lines.Count do
richedit1.Print(richedit1.lines[i]);

Printers and Canvas

Where a Print method is not available you must use the facilities of the Printers unit. The first step is to include the Printers unit in the list of included units in the uses clause:

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For
ms,
Dialogs, StdCtrls, unit1, unit2, Printers;

This provides a Printer object to include in your code.

The second step in printing is to use the printer dialogue box to select and set up a printer. This is done by including a print dialogue option from the Dialogues component set. This is often done with an if statement because the dialogue box includes OK and Cancel buttons which return true and false. If the user clicks OK the printer settings are accepted, if Cancel is selected the printer settings are ignored and the subsequent printing code is ignored:

if PrintDialog1.Execute then
  with Printer do
    begin
      BeginDoc;
      for i:=0 to Memo1.Lines.Count do
        Canvas.TextOut(200,200 + 
          (i * Canvas.TextHeight(Memo1.Lines.Strings[i])),Memo1.Lines.Strings[i]);
      Refresh;
      EndDoc;
    end;

This routine executes the PrintDialog dialogue box and runs the code that follows if the user clicks the OK button. With a combobox:

if PrintDialog1.Execute then
  with Printer do
    begin
      BeginDoc;
      for i:=1 to Combobox1.Items.Count -1 do
        Canvas.TextOut(200,200 + 
          (i * Canvas.TextHeight(Combobox1.Items[i])), Combobox1.Items[i]);
      Refresh;
      EndDoc;
    end;

This will produce a single page of output only. If the output extends over more than one output page we need something more complex:

procedure TForm2.Button1Click(Sender: TObject);
const
 MaxLines=59; 
//60 lines including 0, RichEdit indexes lines from 0
var
 LineCount, TextLine, CurrentLine:integer;
 j,k:integer;
 page, FirstPage, LastPage : integer;
begin
 for j:=1 to 100 do
 RichEdit1.Lines.Add(inttostr(j));  
//set up sample output
 FirstPage:=1;
 LastPage:=RichEdit1.Lines.Count div MaxLines + 1;  
//calculate number of pages
 page:=FirstPage;
 CurrentLine:=0;
 if PrintDialog1.Execute then
 begin
  Printer.BeginDoc;
  for k:= FirstPage to LastPage do  
//loop through pages
  begin
   TextLine:=0;
   //initialise line number for output to page
   for LineCount := CurrentLine to (CurrentLine + MaxLines) do  
//loop from current line to last line on page
   begin
    Canvas.TextOut(200,200 + TextLine * Printer.Canvas.TextHeight(RichEdit1.Lines[TextLine]), RichEdit1.Lines[LineCount]);
    inc(TextLine);  
//TextLine is used to step through the lines on a page
   end;
   inc(page);  
//next page
   CurrentLine := CurrentLine + MaxLines + 1;
  //set first line for next page
   if page <= FirstPage then Printer.NewPage;  
//add new page
  end;
  Printer.EndDoc;
 end;
end;

This code uses two nested loops to go through the pages in a document and then the lines on each page. If the number of lines per page is 60 and the number of lines in 200 then 4 pages will be required (200 div 60=3, add 1 for the remaining 20 lines). The loop through the lines on a page needs to start from the current point reached in the document. Each page, however, needs to be printed from 0 to the number of lines. This requires extra calculation inside the loop. The object name 'Printer' has been left in this code to show the associated methods, these could be replaced with 'With Printer do', as in the earlier code. For different print scenarios the number of lines and the height of the text could be changed.

The code illustrates a number of features of the Printer object:

Printer

This creates a global instance of a TPrinter object, which is used to print to a text file or the printer canvas.

BeginDoc/EndDoc

BeginDoc starts a print job; the job is not sent until the EndDoc procedure is called.

Refresh

This returns the state of the printer canvas to its default state, without resetting the font, brush or pen properties. Without this procedure the printer may produce unpredictable results.

Canvas

A printer object has a canvas, which is the surface on which print commands are run. The code above creates a rectangle 200 pixels from the edges of the page and then starts printing at a point 200 pixels from the top left corner. This code prints the contents of a memo by cycling through its lines and printing each one at a distance of i * Canvas.TextHeight down the page. The print location is set to (200, 200+the line counter * the height of the line).

A canvas has font properties, which can be set as follows:

with Printer.Canvas do
  begin
    font.Name:='Arial';
    font.Size:=12;
    font.style:=[fsBold, fsItalic, fsUnderline, fsStrikeout];  //deliberately uses the 4 styles available
    font.color:='clRed';
  end;

The code below is almost identical but prints the contents of a string grid:

 if PrintDialog1.Execute then
  with Printer do
   begin
    BeginDoc;
    for i:=0 to 15 do
     begin
      line:=stringgrid1.cells[0,i] + ' £' + stringgrid1.cells[1,i];
      Canvas.TextOut(200,200 + (i * Canvas.TextHeight(Stringgrid1.cells[0,i])),line);
     end;
    Refresh;
    EndDoc;
   end;

This shows that similar principles apply to a range of cases and that code can be quickly adapted to new situations.

Each section in a print dialogue box can be accessed separately:

PrintDialog1.Options := [poPageNums, poSelection];
PrintDialog1.FromPage := 1;
PrintDialog1.MinPage := 1;
PrintDialog1.ToPage := PageControl1.PageCount;
PrintDialog1.MaxPage := PageControl1.PageCount;

NewPage

This creates a new page in the printer output stream.

File Output

These printing routines will print whatever has been entered in a Memo or RichEdit control. Rather than typing the data into these controls we could send lines of text to them and then print them; we could obtain the lines of text from a file and we would thus have a reporting tool capable of displaying and printing the contents of a file. For example:

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
begin
 memo1.lines.Add(format('%-30s',['Geoffrey Thompson'])+ format('%20s',['West Ham United']));
 if PrintDialog1.Execute then
  with Printer do
  begin
   canvas.Font.Size:=20;
   canvas.Font.name:='Courier New'; 
//fixed pitch font to keep characters in strict columns
   BeginDoc;
   for i:=0 to Memo1.Lines.Count do
    Canvas.TextOut(200,200 +
     (i * Canvas.TextHeight(Memo1.Lines.Strings[i])),Memo1.Lines.Strings[i]);
   Refresh;
   EndDoc;
end;

We could direct output from a text file to the memo and then call this print routine (minus the memo1.lines.add line):

procedure TForm1.Open1Click(Sender: TObject);
var F:textfile;
     S:string;
begin
 if OpenDialog1.execute then
 begin
  AssignFile(F, OpenDialog1.FileName);
  Reset(F);
  While not eof (F) do
  begin
   Readln(F, S);
   Memo1.Lines.Add(S);
  end;
  CloseFile(F);
 end;
end;

This File/Open routine has been placed in a menu.

Similarly we could take output from a structured file, format it appropriately and output it to a memo before printing it.

procedure TForm1.Open2Click(Sender: TObject);
var
 memostring:string;
begin
 memo1.Font.Name:='Courier New'; 
//fixed pitch font to keep characters in strict columns
 assignfile(personfile,'persons.dat');
 reset(personfile);
 while not eof (personfile) do
 begin
  read(personfile,person);
  memostring:=format('%-20s',[person.name]) +
   format('%-20s',[person.address]) + format('%10s',[datetostr(person.dateofbirth)]);
  memo1.Lines.Add(memostring);
 end;
end;

This File/Open routine has been placed in a menu, either as a replacement for the text file routine above or as a new option below it.

Printing to a Text File

An alternative approach to printing is to use the AssignPrn procedure, which directs text output to the printer. For example:

declaration: FPrn:textfile; //(can also use System.Text)
AssignPrn(FPrn);
Rewrite(FPrn);
writeln(FPrn,'Daily Management Statistics');
writeln(FPrn);
write(FPrn,'Date: ');
writeln(FPrn, cons_record.day,'/',cons_record.month,'/',cons_record.year);
write(FPrn,'Number of Parcels: ');
writeln(FPrn, num_parcels);
writeln(FPrn, total_weight_string);
writeln(FPrn, total_revenue_string);
System.CloseFile(MyFile);

Formatting Output

Simply sending text to an output stream, whether to a text file or a canvas, may not produce the desired results, the output will have to be formatted. An application like a spreadsheet or a table in a word processor or HTML editor has formatting options available such as alignment and number of decimal places. In the examples above the text to be output was already formatted in a memo or string grid but you could also use the formatting functions when sending data to a printer stream.

In the following example the Assign(FPrn) option is used:

if PrintDialog1.Execute then
begin
 AssignPrn(FPrn);
 Rewrite(FPrn);
 try
  with Printer.Canvas do
   begin
    font.Name:='Courier New';
    font.Size:=12;
    font.style:=[];
   end;
 total_weight_string:=Format('Total weight: %fkg',[total_weight]);
 total_revenue_string:=Format('Total cost: £%f',[total_revenue]);
 writeln(FPrn,'Daily Management Statistics');
 writeln(FPrn);
 write(FPrn,'Date: ');
 writeln(FPrn, cons_record.day,'/',cons_record.month,'/',cons_record.year);
 write(FPrn,'Number of Parcels: ');
 writeln(FPrn, num_parcels);
 writeln(FPrn, total_weight_string);
 writeln(FPrn, total_revenue_string);
 finally
  closefile(FPrn);
end;

Back to Delphi Menu