by Aaron Ballman

One issue I see people stumble over time and again is how to properly print a document in REALbasic. If you're just doing a quick-and-dirty print job, then things seem to work great without any real trouble. Where you start to run into problems is when you want to do the correct thing when the user selects printing options like multiple copies or page ranges. This article, and the accompanying project, are meant to provide you with the information you need to properly handle all printing situations. You will need REALbasic 5.5.4, but no prior printing knowledge is required.

User Interaction

Let's start off by discussing the typical user experience when it comes to printing. Usually, there are two menu items related to printing that live on the File menu. The first one is called "Page Setup", and the second is "Print". The job of page setup is to set various printing preferences pertaining to the page layout. You usually set things like the margins and type of paper to be used in this dialog. In REALbasic, you display this dialog by making a new PrinterSetup object and calling PageSetupDialog on it. This function displays the system Page Setup dialog (modally), and when it returns, the PrinterSetup information is properly filled out based on the user's choices.

The other standard dialog used in many applications is the Print dialog. This dialog allows the user to select which printer to print to, as well as some more options such as how many copies to print. To display this dialog in RB, you use the global method OpenPrinterDialog (again, a modal dialog). If you created a PrinterSetup object, you will want to pass this into the call so that the options the user chose in the Page Setup dialog can be applied to the printer they select. This function returns a Graphics object for you to print to. If the user cancelled the dialog box, the Graphics object will be nil. Once you have obtained a non-nil Graphics object, you're ready to start printing.

Printing Basics

The basic thought behind printing is that you are given a blank sheet of paper, and you can draw anything you want onto it. Once you are done drawing to a sheet, you can tell REALbasic to print it and you'll get a brand new blank sheet to draw on. Also, REALbasic will tell you all sorts of information about what the user would like, such as the range of pages to print or how many copies to print. I realize that all of this sounds rather simplistic, but it all really does boil down to this basic concept.

The first thing to realize is that the Graphics object you've been given has a lot of information on it. And if you can't find the information you're looking for there, then it will most likely end up in the PrinterSetup object instead. For example, the number of copies and the page range information are both stored on the Graphics class. So you should pay attention to these options or else your print job will not match what the user expects. The main pieces of information you should pay attention to are:

  • Graphics.Copies
  • Graphics.FirstPage
  • Graphics.LastPage
  • PrinterSetup.Width
  • PrinterSetup.Height

You may be wondering why I didn't mention anything about margins. That is because REALbasic handles margins for you automatically. PrinterSetup.Left and PrinterSetup.Top refer to the top and left printable portion of the page, which is always 0, 0. If you need to know how large the margin is, then PrinterSetup.PageTop and PageLeft tell you the number of pixels from the margin to the edge of the page. Read that carefully... from the margin to the page edge. This means that the PageTop and PageLeft properties will return a negative number to you. In any case, when you're printing, you can assume that the origin for all printing functions is already taking the margin information into account. Another handy thing is that the PrinterSetup.Width and Height properties also account for margins. So if you were to draw a line from 0, 0 to PrinterSetup.Width, PrinterSetup.Height, the line would start at the left and top margin and travel diagonally down to the bottom, right margin. Furthermore, you don't need to do anything special to handle landscape printing -- just check to see if the width > height if you need to print differently. In most situations, you won't want to do anything special, just be sure to print all the way out to the width of the page and landscaping will happen automatically.

Now would also be a good time to mention that all information on the Graphics and PrinterSetup class is in pixels (not twips, like it is if you're coming from VisualBasic). The only exceptions to this rule are the resolution specifiers (PrinterSetup.[Max]Horizontal/VertialResolution), which are in dots per inch.

So now that you understand a bit more about the metrics involved with printing, I want to take a minute to discuss one of the fundamental concepts that I see people constantly confused by -- how to complete the printing of a page. There are two ways for you to signal to REALbasic that you are done printing, and both have different side effects and should be used in different situations.

The first way to signal that you're done with the current page is by calling Graphics.NextPage. This tells REALbasic that you're done with the current page and it can be sent off to the spooler to be printed. It also wipes the Graphics object clean (so to speak), so that it is ready for you to start drawing to another page. This is a very important thing to keep in mind. Calling NextPage means you're committing to having another page to print out.

The second way to signal that you're done with the current page is by setting the Graphics object to nil (or letting it go out of scope). In essence, this tells REALbasic two things -- the first is that it should send the current page off to the spooler, and the second is that it should tell the printer that the current job is complete. This means that you should only let the Graphics object go out of scope and be released when you are done printing the current job. If you remember, calling NextPage wipes the Graphics object clean so that it's ready for another page. So if you called NextPage to force the page off to the printer, and then let the Graphics object die naturally, you will end up with a blank page.

One thing that I should mention (though it may be obvious to the astute reader) is that because you have a Graphics object, you can use any function of the Graphics class. So you can draw lines, rectangles, text, images, etc and they will show up on your printout. There are some limitations as to what you can print, but those are typically platform specific. I won't be getting into those in this article since the focus is on how to properly print simple text.

StyledTextPrinter

So now that you know you can use Graphics.DrawString (or other calls) to draw to the printed page, I want to discuss a very powerful feature for drawing large amounts of text.

One of the most under-utilized printing features of REALbasic is the StyledTextPrinter class. Don't let the class name fool you; you can use it for printing any text data, whether its styled or not. If the text is styled, the style information is retained when printing the text. In order to get a StyledTextPrinter, you need an EditField with the Styled (and usually Multiline) property set to true. This EditField can be on-screen, such as a word processor might have. But it can also be off-screen, on a hidden window where the user will never see it. In either case, once you have the EditField, you can obtain a StyledTextPrinter object from it using EditField.StyledTextPrinter. The object is created with a specific width in mind and is based on a Graphics object. Note that you can use a printer Graphics object, or any other Graphics (such as a Canvas or a Window). The StyledTextPrinter object is used to draw blocks of text. When you initially create the object, you tell it how wide the blocks will be, and when you go to draw a block, you tell it how tall you want it to be. The object will then draw the block of text, as much as will fit based on the given dimensions. You can then draw another block, and it will automatically start from where it left off before and draw the remaining text.

This feature can be extremely useful for doing things like drawing columns of text. But what I like to use it for is to draw entire pages of text. I do this because of the fact that it automatically keeps track of how much text has been printed so far. This means I can easily use it to page ranges that will be accurate. Another nice feature is that you can use it to do a WYSIWYG print preview for the user since you draw to a Canvas with the StyledTextPrinter.

Putting It All Together

If you haven't already done so, you should download the sample project found here.

Due to the copious use of comments in the sample project, I am only going to give you the 50,000 ft overview of how to properly print a large amount of text. The basic steps are as follows:

  1. Obtain the page setup and printer information by displaying the proper dialogs to the user. Keep in mind that not everyone will go to the Page Setup menu, so have decent default margins in mind.
  2. Factor all your printing code into methods so that you can easily print entire copies of a job. I like to have a method that is responsible for printing one copy of the document. Make a loop that uses Graphics.Copies and calls this method so that the user gets the number of copies they were expecting.
  3. In the method to print a single copy, you should start out by making the StyledTextPrinter object. Then pay attention to Graphics.FirstPage and Graphics.LastPage. Note that you can't just loop from first to last since you don't know how much text will be printed on the page you're omitting.
  4. Starting with page one, print a single page of information, and then check to see whether you want to send that page to the spooler (by calling Graphics.NextPage, or letting the Graphics object go to nil), or whether you want to skip that page. If you wish to skip the page, just call Graphics.ClearRect on it to wipe away the information you already printed.
  5. Make sure you end on the right page. Basically, keep in mind that the last page shouldn't be calling Graphics.NextPage since the Graphics object will automatically print it for you when it goes out of scope.
  6. Do general cleanup code if you need to so that you're ready for another print job.

By following this simple outline, your users will get the document they expected (with the right number of copies, etc) and you won't have to do very much work to accomplish it. Hopefully you've learned enough about printing to be able to strike out on your own and do more complex things than just printing pages of text. If you have any questions or comments, feel free to email me at aaron@realsoftware.com.