
Introduction
Multipad is my Notepad clone. It also has the typical Page Setup, Print Preview and Print menu items. Read on if you want to know how printing works in Multipad. It is best to download the source code and take a look at it while reading the article.
The PrintDocument
PrintDocument
is a .NET class which glues together the page settings, the printer settings, the data to print and the print logic. For Multipad, I derived a class from PrintDocument
called MultipadPrintDocument
. It has properties to set the text (which can contain Environment.NewLine
s) and the font.
MultipadPrintDocument
implements the virtual
methods OnBeginPrint()
and OnPrintPage()
. OnBeginPrint()
sets the pointer to the current character (_offset
) to 0 and the current page number to 1.
OnPrintPage()
has one parameter of type PrintPageEventArgs
. When the system calls OnPrintPage()
, we have to typeset the page using the Graphics
object in the PrintPageEventArgs
parameter. We typeset the page by printing lines of text until the page is full or until we reach the end of the text. The PrintPageEventArgs
parameter has a property MarginBounds
which contains the page size. Unfortunately, the dimensions are expressed in hundredths of an inch. In .NET, there is no GraphicsUnit
which represents a hundredth of an inch. Because we're going to measure strings in GraphicsUnit.Document
(1/300th of an inch), we convert the page dimensions by multiplying them with 3. Now we're ready to start printing the lines of the text.
First, we create a StringBuilder
which will hold the text of a single line. We also create a GenericTypographic
StringFormat
object. It's very important to use GenericTypographic
because otherwise, MeasureString()
and DrawString()
wouldn't behave as expected. We also set the tab stops: if a line of text contains tabs, the text will be formatted correctly (the tabs will not be replaced with spaces, they will act as real tabs). Finally, we set the PageUnit
property of the Graphics
object to GraphicsUnit.Document
.
Before we start filling the page with lines, we check to see if there is enough space to print at least four lines: one line of text and three lines for the page number (two empty lines above the page number). If there isn't enough room, we simply leave the page blank. This situation should rarely occur.
Now we're ready to break up the text in lines. We read the text string one character at a time. We add the character to the line buffer (the StringBuilder
object), except if the character is a NewLine
or Eos
(end of string). Note that NewLine
is "\r\n" in Windows or "\n" in Unix, so we have to keep in mind that we don't simply skip one character but Environment.NewLine.Length
characters (yes, we strive to perfection). If the character is a space or a tab, we save this position because when the line is full, we don't want to break the line in the middle of a word, no, we will print the line up to the last tab or space. For example, if we have the line "The quick brown ... lazy dog." and the line overflows at the 'd' of "dog", we will begin the next line with "dog" instead of "og". If the last character is a space or tab, we continue to add these characters. This way, the next line will never start with white space. If the line overflows or we encounter a NewLine
or Eos
, we print the text in the line buffer. Then we increment the y position of the next line and empty the line buffer. If there's still room for a new line of text and we still have text to print, we continue the loop; if not, we exit the loop, print the page number at the bottom of the page, increment the page number and set the property HasMorePages
of the PrintPageEventArgs
parameter to true
if there's still text to print, otherwise we set it to false
.
Using MultipadPrintDocument in your own code
If you want to use MultipadPrintDocument
in your own code, it's best to rename the class and its constructor to something more appropriate. Then add a variable of type XXXPrintDocument
to your form and implement the menu items as follows:
void OnFilePrint(Object sender, EventArgs e)
{
PrintDialog pd = new PrintDialog();
_printdocument.Text = _multipadbox.Text;
_printdocument.Font = _multipadbox.Font;
pd.Document = _printdocument;
pd.ShowDialog();
}
void OnFilePrintPreview(Object sender, EventArgs e)
{
PrintPreviewDialog ppd = new PrintPreviewDialog();
_printdocument.Text = _multipadbox.Text;
_printdocument.Font = _multipadbox.Font;
ppd.Document = _printdocument;
ppd.ShowDialog();
}
void OnFilePageSetup(Object sender, EventArgs e)
{
PageSetupDialog psd = new PageSetupDialog();
psd.Document = _printdocument;
psd.ShowDialog();
}