6

 

Printing

 

One facility that every programmer in the world would want to provide through his program is that of printing. The .NET world has bestowed about ten important printer classes that ease our job of printing text and graphics to the printer. We shall grapple with some small programs to grasp the concepts of printing and conclude this chapter with a small word processor program that is provided along with the .NET samples. You should create a text file named a.txt in the current subdirectory, and thereafter, insert the following three lines:

 

a.txt

hi, how are you

bye, take care

end

 

a.cs

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz {

Font f;

StreamReader sr;

public void abc()

{

sr = new StreamReader ("a.txt");

f= new Font("Courier New", 14);

PrintDocument pd = new PrintDocument();

pd.PrintPage += new PrintPageEventHandler(pqr);

pd.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

float lpp = e.MarginBounds.Height  / f.GetHeight(e.Graphics) ;

int c = 0 ;

String s=null;

while (c < lpp && ((s=sr.ReadLine()) != null))

{

float y = e.MarginBounds.Top + (c * f.GetHeight(e.Graphics));

e.Graphics.DrawString (s, f, Brushes.Black, e.MarginBounds.Left, y);

c++;

}

if (s != null)

e.HasMorePages = true ;

else

e.HasMorePages = false ;

}

public static void Main() {

zzz a = new zzz();

a.abc();

}

}

 

The above program prints the contents of the text file a.txt to the default printer. In all our programs in this chapter, we shall create an object that is an instance of class zzz and call function abc off it. The code within this function will execute the actual task of printing.

 

We first create an object, sr, which is an instance of the class StreamReader. The constructor of this class is furnished with the name of the file that we want to print, i.e. a.txt.

 

The StreamReader class is derived from the class TextReader, which reads characters from a stream of bytes that have been encoded in a particular format. The TextReader class belongs to the System.IO namespace. The Stream class too forms a part of the same genre. This class is designed for reading byte streams.

 

The StreamReader class is optimised for reading lines of data from an ASCII or a text file. By default, the encoding used is UTF-8, but it can be changed to any other encoding format, if required. The UTF-8 encoding can comprehend Unicode characters. The StreamReader class, by default, is not thread safe, and neither does it belong to the Printing classes in the .NET family.  The class is used in this program to read the file, one line at a time.

 

We want the printer to print our text file in a specific font. So, we create a Font object f, whose constructor is passed the Font Name 'Courier New' along with the size of '14 points'. We have plenty of choice of fonts from the innumerable fonts available. For the neophytes of the publishing world, it would be a revelation that 72 points makes an inch. If we specify a font name such as 'Vijay Mukhi', which does not exist, the printer uses the default font instead, which in this case, is Microsoft Sans Serif. Thus, the printer always receives a valid font type to work with.

 

We create one more object called pd, in our program, which is an instance of class PrintDocument. This class is the nucleus of all printing activities in the .NET world. It has an event called PrintPage, which requires an instance of a delegate PrintPageEventHandler. The delegate represents a method called pqr, which handles the PrintPage event. This event is generated by the Print function.

 

Thus, when we call the Print function, it in turn, calls the pqr function. This function is passed two parameters. The second parameter is a PrintPageEventArgs class, which carries data that is useful for printing. The PrintPageEventArgs class is derived from class EventArgs. The pqr function is not called explicitly, since this would entail creation of an object like PrintPageEventArgs.

 

The Print function is a blocking function. This implies that not a single line of code will be executed after the Print function has been called, until the pqr function completes execution. If the pqr function goes into an indefinite loop, the code succeeding the Print function will never get executed.

As mentioned earlier, the second parameter e of data type PrintPageEventArgs contains the data that is to be printed. The member MarginBounds, which is of data type Rectangle, specifies the rectangular portion of the page, which falls within the margins. In our case, the various values of the Rectangle structure are as follows:

Height = 900, Width = 650,         Left = 100, Right = 750

Top = 100, Bottom = 1000,           X = 100, Y = 100.

 

Before we initiate printing, we need to identify the number of lines that can be printed on a single page. To estimate this, we use the following calculation:

     Height of our page in pixels divided by the height of the font in pixels.

 

In this example, it works out to 900 / 22.02691 = 40.85. This value is stored in the variable lpp, which signifies 'lines per page'.

 

We then use another variable c, which has an initial value of zero. It is incremented in the while loop, once for every line that is printed. As soon as the value of c exceeds that of lpp, the program quits out of the 'while' loop.

 

The ReadLine function of the StreamReader class fetches a new line from the file a.txt and stores it within a string variable s. If there are no more lines to be read from the file, this function returns a null. Thus, the while loop terminates when the number of lines printed exceed the total number of lines that can fit on a page or when there are no more lines to be read from the file.

 

A check must be performed to verify whether we have printed the maximum possible lines that can fit on a page, before we read the next line from the file. This is to forestall reading a line from the file and thereafter, realizing that it cannot be printed on the page due to lack of space. To perform this check, we determine the y position of each line on the page. All text is printed below the Top margin. So to calculate the y position of a line, we use the following calculation:

(Height of the font used MULTIPLIED BY the number of lines printed so far) PLUS value of the Top margin.

In this case,

The value of the top margin is 100

The height of the font is 22.02691

 

In the case of the first line to be printed, the number of lines printed so far is 0.

 

So, the value of the y co-ordinate for the first line is

     =100+(22.02691 X 0) = 100.

 

The value of the y co-ordinate for the second line is

     =100+(22.02691 X 1) = 122.0269.

 

The value of the y co-ordinate for the third line is

     =100+(22.02691 X 2) = 144.0538.

 

We accessed these values using the WriteLine function.

 

The Graphics member in class PrintPageEventArgs is employed to write to the printer. This member contains the function DrawString. This function, which we had used earlier to write to the screen, is now utilized to write to the printer.

 

The DrawString function accepts 5 parameters:

1) A string, s

2) A font, f

3) A color or brush, Black

4) The left margin for the x coordinate, MarginBounds.Left

5) The y co-ordinate

 

As you may have noticed, the same DrawString function is used for printing to the printer and writing to the screen. Abstraction, therefore, enables us exploit and reuse concepts that we have learnt earlier.

 

The while loop ends when any of the following two conditions is satisfied:

     The number of lines printed exceeds the lines per page i.e. when we need to print on the next page.

     The file has no more lines to print.

To figure out which of the above conditions has ended the 'while' loop, we verify the value contained in string s. If the value is null, it signifies that there are no more lines to print.

 

As soon as we exceed the maximum number of lines that can be printed on a page, and there are lines remaining to be printed, there has to be a mechanism to inform the PrintDocument class that the function pqr needs to be called again, to print the next page. To accomplish this, the HasMorePages member of the PrintPageEventArgs object e is either set to true or false. If it is set to true, the function pqr gets called again.

 

a.cs

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

Font f;

public void abc()

{

f= new Font("Courier New", 14);

PrintDocument pd = new PrintDocument();

pd.PrintPage += new PrintPageEventHandler(pqr);

pd.PrintPage += new PrintPageEventHandler(xyz);

pd.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Vijay Mukhi", f, Brushes.Black, 100, 200);

}

void xyz(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", f, Brushes.Black , 200, 300);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

 

Through this example, we once again demonstrate the power of events and delegates. We attach two methods, pqr and xyz, with the printing event. The first function pqr prints 'Vijay Mukhi' at X=100 and Y=200 and then, the method xyz prints 'Sonal Mukhi' at X=200 and Y=300. The HasMorePages property is initialized to false by the program, even though it has the value of false by default. Both these lines get printed on the same page.

 

Each time we set HasMorePages to true, the .NET framework sends a 'form feed' signal to the printer that sets up a new page for printing. The only mechanism of printing text to a printer is by means of the Graphics object.

 

If the 'font' and the 'Brushes' parameters are set to null, a run-time exception is generated. Hence, they must be explicitly specified. Unlike a dot matrix printer that prints one line at a time, a laser printer first creates an entire page in memory and then sends it for printing. Thus, in the function pqr, if we change the Y coordinate from 200 to 400, both lines get printed on the same page, since the page first gets created in the memory, and then it is sent to the printer.

 

The PrintDocument class creates the PrintPageEventArgs object, since that is the sole mechanism of activating the printer. Then, it calls all the methods registered through the delegate.

 

a.cs

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

yyy  y = new yyy();

y.PrintPage += new PrintPageEventHandler(pqr);

y.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

System.Console.WriteLine("pqr");

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

public class yyy : PrintDocument

{

Font f;

protected override void OnQueryPageSettings(QueryPageSettingsEventArgs e)

{

base.OnQueryPageSettings(e) ;

System.Console.WriteLine("OnQueryPageSettings");

}

protected override void OnBeginPrint(PrintEventArgs e)

{

base.OnBeginPrint(e) ;

System.Console.WriteLine("OnBeginPrint");

f = new Font("Courier New",14);

}

protected override void OnEndPrint(PrintEventArgs e)

{

base.OnEndPrint(e) ;

System.Console.WriteLine("OnEndPrint");

}

protected override void OnPrintPage(PrintPageEventArgs e)

{

base.OnPrintPage(e) ;

System.Console.WriteLine("OnPrintPage");

e.Graphics.DrawString ("Vijay Mukhi", f, Brushes.Black, 100, 400);

}

}

 

Output

OnBeginPrint

OnQueryPageSettings

pqr

OnPrintPage

OnEndPrint

 

In the above example, class yyy is derived from PrintDocument. Thus, all code embodied in the PrintDocument class is available to yyy. This also implies that any code in the class can be overridden. The Print method of the PrintDocument class had earlier called the methods registered with the delegate, whereas now, it calls a series of methods from the PrintDocument class. In the situation where we want to call our methods with the same name as in class yyy, the keyword 'override' has to be used.

 

The output confirms that the first function to be called is OnBeginPrint. This function is akin to an initialization function. All code that is to be executed only once must be placed in this function. It is a sage programming practice to always call the original function from the base class. Thus, we use the keyword base to call the original OnBeginPrint from the class PrintDocument.

 

Superior programming style decrees that, initially, the base functions should be called. It is not mandatory to do so, since even if this is not done, the class still behaves in a similar manner. The documentation too is silent on this issue, but we consider it prudent to do so and advise you also to call the original function first.

 

The parameter PrintEventArgs passed to the function has no significant role to play in this release. The documentation clearly states that this PrintEventArgs has been designed for use in a future release. Thus, it cannot be put to any consequential use at present. We create a Font object in this function, since it is a one-time activity.

 

The second function that gets called is OnQueryPageSettings, which will be elucidated in the next example. Following it, function pqr gets called, which facilitates printing.  Then, function OnPrintPage gets called with the same parameters as that of the function pqr. If we so desire, we can print in the delegate method. Other than the delegate method being called first, there is no apparent difference.

 

Finally, the function OnEndPrint is called, in which, we are presented with the opportunity to clean up any resources created in the method OnBeginPrint.

 

a.cs

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

yyy  y = new yyy();

y.PrintPage += new PrintPageEventHandler(pqr);

y.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

public class yyy : PrintDocument

{

Font f;

protected override void OnQueryPageSettings (QueryPageSettingsEventArgs e)

{

base.OnQueryPageSettings(e) ;

e.PageSettings.Landscape = true;

}

protected override void OnBeginPrint(PrintEventArgs e)

{

base.OnBeginPrint(e) ;

f = new Font("Courier New",14);

}

protected override void OnPrintPage(PrintPageEventArgs e)

{

base.OnPrintPage(e) ;

e.Graphics.DrawString ("Vijay Mukhi", f, Brushes.Black, 100, 400);

}

}

 

Each time we use the printer, we may be desirous of customizing the print settings for each page. To enable this, the framework calls the function OnQueryPageSettings before printing any page. In this function, we can change the page settings using the parameter e, which is of type QueryPageSettingsEventArgs. This class is derived from PrintEventArgs class.

 

One of the members in QueryPageSettingEventArgs is PageSettings, which contains a large number of properties that control the printer settings. Here, we have only used the Landscape property and assigned it a boolean value of true. Had we assigned it a value of false, the property would have been set to Portrait mode, which is usually the default mode.

 

Other properties that can be modified are: the paper tray i.e. the source from where the paper is fed to the printer, the printer resolution, the page margins, the color etc. Any property of a printer that can be set manually can now be done using this function.

 

a.cs

using System;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

yyy  y = new yyy();

y.PrintPage += new PrintPageEventHandler(pqr);

y.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

public class yyy : PrintDocument

{

Font f;

protected override void OnPrintPage(PrintPageEventArgs e)

{

base.OnPrintPage(e) ;

e.Cancel = true;

f = new Font("Arial",13);

e.Graphics.DrawString ("Vijay Mukhi", f, Brushes.Black, 100, 400);

}

}

 

In this program, the Cancel property of PrintPageEventArgs is set to true, resulting in cancellation of the print job. The printing that was to be carried out by the delegate function also gets cancelled. All print jobs are aborted. The PageSettings object used in the earlier program is also a property of the PrintPageEventArgs class, and thus, can be used in this function too.

 

a.cs

using System;

using System.Windows.Forms;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

PrintDocument y = new PrintDocument ();

y.PrintPage += new PrintPageEventHandler(pqr);

PrintDialog d = new PrintDialog() ;

d.Document = y;

DialogResult r = d.ShowDialog();

if (r == DialogResult.OK)

y.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

 

In this example, we would like to display a dialog box to the user where he can change the printing options. Therefore, firstly we create an object d as an instance of class PrintDialog. Creating a PrintDialog object assigns default values to the following eight properties:

 

     AllowSomePages is set to False, disabling the Pages option button.

     AllowSelection is set to False, disabling the Selection option button. 

     AllowPrintToFile is set to True, enabling Print to File check box.

     Document, which is a reference to the PrintDocument object, is set to null.

     PrinterSettings is assigned the value of False, disabling the dialog box.

     PrintToFile decides whether the printer would print to disk or to the printer. If the value is null, it prints to the printer.

     ShowHelp is set to False, thus disabling the help button.

     ShowNetwork is set to True, thus enabling the network button.

 

We can modify any of these values to befit our requirements. We can also select specific parts of the document to print.

 

Screen 6.1

 

When we run the program, the normal Windows Printer Dialog box is displayed, allowing us to modify any of the properties. The ShowDialog function facilitates the display of the dialog box. This function waits until one of two the buttons, OK or Cancel, is clicked.The value returned on clicking one of these two buttons is stored in the DialogResult object r.

 

If the OK button is selected, it will start the printing, whereas if the Cancel button is selected, it will cancel or abort the printing.  When the OK button is selected, the function ShowDialog returns an enum DialogResult, with a value OK. The above enum can have as many as 8 different values, depending upon the type of buttons available in the Dialog Box. Thereafter, the Print function of the PrintDialog class is called.

 

One of the final actions of the function ShowDialog is to initialize the Document property supplied by PrintDocument object, i.e. y, to the printer settings chosen in the Dialog box.

 

a.cs

using System;

using System.Windows.Forms;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

PrintDocument y = new PrintDocument ();

y.PrintPage += new PrintPageEventHandler(pqr);

PageSetupDialog p = new PageSetupDialog () ;

PageSettings s = new PageSettings();

p.PageSettings = s ;

p.ShowDialog ();

y.DefaultPageSettings = s;

y.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

 

In the above example we usher in a new class, PageSetupDialog, which allows the user to change page settings such as the margins and the paper orientation. When we create a PageSetupDialog object, properties in the object get initialized to their default values.

 

Some of the properties are :

     AllowMargins: set to true, enables us to change the margins settings.

     AllowOrentation:  set to true, enables the Portrait/Landscape radio buttons.

     AllowPaper: set to true, so that we can choose the paper size and source.

     AllowPrinter: set to true, enables the printer button.

     MinMargins: set to null, so that the default margins of 1 inch are displayed.

     PageSettings: set to null.

     PrinterSettings: set to null.

 

It is mandatory to initialise the PageSettings property to an actual object containing the settings selected by the user. Finally the function ShowDialog initializes the object s.

 

Screen 6.2

 

Then, the property DefaultPageSettings of the PrintDocument class is initialised to the PageSettings object s, so that the printing framework uses the settings stored in this property. Thus, the two dialog boxes have their own unique purposes, and each of them fills up an object that is used by the PrintDocument class. In the Windows world, Dialog boxes are used to fill up properties/variables in an object.

 

In the above two programs, the function pqr does not receive any values from the dialog boxes.

 

a.cs

using System;

using System.Windows.Forms;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz {

public void abc()

{

PrintDocument pd = new PrintDocument();

pd.PrintPage += new PrintPageEventHandler(pqr);

PrintPreviewDialog dlg = new PrintPreviewDialog() ;

dlg.Document = pd;

dlg.ShowDialog();

pd.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 200);

}

public static void Main() {

zzz a = new zzz();

a.abc();

}

}

 

Screen 6.3

 

Before we buy goods, we often like to preview them. The same analogy is true when we want to print a document. The class PrintPreviewDialog enables us to preview a document before printing it. The only property that is to be initialized here is Document property. This is mandatory since there is no other mechanism by which the Dialog box can receive the contents of the page.

 

We can safely assume that the ShowDialog function calls Print, which figures out the text to be printed on a page. It also has the ability to display the content in different magnification percentages and in multiple pages at a time. The Dialog contains a PrintPreviewControl, which has several members that can be toyed around with, in order to transform the appearance.

 

 

a.cs

using System;

using System.Windows.Forms;

using System.Drawing;

using System.Drawing.Printing;

using System.IO;

public class zzz

{

public void abc()

{

PrintDocument pd = new PrintDocument();

pd.PrintPage += new PrintPageEventHandler(pqr);

pd.Print();

}

void pqr(object o, PrintPageEventArgs e)

{

Image i = Image.FromFile("Sample.jpg");

Point p = new Point(100, 100);

e.Graphics.DrawImage(i, p);

e.Graphics.DrawString ("Sonal Mukhi", new Font("Courier New",10), Brushes.Black, 100, 600);

}

public static void Main()

{

zzz a = new zzz();

a.abc();

}

}

 

A printer has the capability of not only printing text, but also lines, ellipses, curves and images. This program prints an image. Before you proceed, ensure that file sample.jpg resides in the current directory.

 

The Image class constructor is supplied with a jpg file, which we have picked up from the .NET samples. The Point object p represents the X, Y coordinates where the image is to be drawn. Then, using the DrawImage function from the Graphics class, the image is printed at the position specified. Using this procedure, all images displayed on the screen in the previous chapters can be sent to the printer for printing.