|Figure 1. Printing and Previewing Plain Text: Here’s the main form of the TextPrinting Project in Part II of this series, along with the print preview form from the same project.|
art I of this solution series explores VB’s basic printing concepts: the Printer object, page geometry, graphics methods, and the Print common dialog control. In this solution, you’ll see how to control where printing occurs on a page.
Building on the knowledge gained from Part I, in Part II you’ll build a text editor and text-printing utility that includes font selection and print-preview capabilities. The VB6 TextBox control provides the base functionality you need to build the text editor; but what good is a text editor without printing capabilities? Figure 1 shows the main form of the TextPrinting project and the print-preview form from the same project. When sent to a printer, the text will print just as it appears on the preview form, within the specified margins.
|Author’s Note: Printing formatted text is a very different story. The RichTextBox control provides a Print method, which you can use to send the control’s text to the printer. Printing formatted text is a different task from printing unformatted text, so I won’t cover the topic in this solution, except to say that your best bet is to either use a third party control or print through Word using OLE automation.|
|Figure 2. Printing ListView Content: The figure shows both a ListView control and the result of printing the contents of that ListView using the project from Part III of this series.|
Finally, in Part III of this solution series you’ll see how to print tabular data by adding two methods, one for previewing and another for printing items of the ListView control. It’s not the most elaborate printing tool, but it’s a nice component to add to any interface that uses the ListView control. Figure 2 shows a ListView control displaying a Customer table in detail mode as well as a preview of the control’s printout. You can use the same technique to print any type of tabular data, including price lists, invoices, and so on. All you have to do is populate the control, set the column widths and call a method to print or preview the items.
Although VB6’s Printer object is convenient, printing in VB has some serious limitations, such as a lack of support for controls for printing and previewing documents (a situation that was rectified in VB.NET). Generating elaborate printouts with VB6 requires a substantial programming effort; therefore most VB6 developers use third party controls for their printouts. But you don’t always want to include a large printing library to gain decent print capabilities; therefore, good printing tools have their place in every developer’s utilities collection.
You don’t have to rely on third-party controls to gain additional printer control in VB. This solution reviews the basics of printing with VB6 and then applies the basic concepts to build some practical tools and utilities.
Basic Printing Concepts
To print with VB6 you must understand a few basic concepts. For typical business applications you need to focus on printing text, lines and frames. Printing text is a straightforward process: you first set up a scale (i.e., whether a millimeter on the paper corresponds to a meter or a mile of the original), determine the dimensions of the strings you want to print and then select a starting point for each string. The following sections explain concepts you’ll use throughout this solution series.
The Printer Object
This object represents the printer, as well as the page on which you’ll print. The Printer object exposes basic printer properties, such as the page’s dimensions and orientation, the quality of the printout, and so on. It also exposes methods for printing basic shapes and text, just like the Form object and the PictureBox control. To draw something on the printer you can call any of the methods discussed in the next section. In addition to these printing methods, the Printer object exposes the NewPage method, which renders and emits the current page, and the EndDoc method, which emits the current page and terminates the printout. You can place graphics elements on the page in any sequence, because the Printer object generates the page’s image in memory. In other words, you might just as easily print the footer and then the header rather than the more natural header-then-footer sequence. The Printer object renders the image to paper only when you call the NewPage method.
VB6 provides a few methods for drawing basic shapes like lines, rectangles, circles and ellipses (the Line and Circle methods), as well as a method for printing text (the Print method). The Form object and the PictureBox control also expose these methods. Each of these methods accepts the basic attributes of the shape they will print as arguments: the string to be printed, the starting and ending point of a line, or the location and radius of a circle. For Forms and PictureBoxes, the output goes to the screen; for the Printer object, it goes to the printer.
|Figure 3. Visualizing a String’s Width and Height: The rectangles shown were drawn using the return values of the TextWidth and TextHeight methods, which return the width and height that a string requires when drawn in an output device’s current font.|
To position text on the page, you need a mechanism to calculate the dimensions of strings. The methods TextWidth and TextHeight accept a string as argument and return the string’s width and height when printed, respectively, taking into consideration the current font of the output device. If you draw a rectangle specified by these two numbers, it will enclose the entire area in which the string will print, leaving a sufficient margin above and below the characters to prevent consecutive lines of text from touching each other. Figure 3 shows a string printed in three different font sizes enclosed within rectangles determined using the results of calling TextWidth and TextHeight methods. You’ll see how to use these methods to align text in columns.
Notice that the TextHeight method always returns a value that accommodates all characters, including upper and lower case characters and symbols. In other words, the method returns the same height regardless of the content of the string you pass to the TextHeight method. The TextWidth method, on the other hand, takes into consideration the widths of the individual characters. A string made up of w’s is wider than a string made of i’s.
The default coordinates for both forms and printers in VB6 are expressed in twips. There are 1,440 twips in an inch. Twips are device-independent units. Coordinates expressed in twips remain accurate when mapping from logical inches to either screen pixels or printer dots, or to any other coordinate system. If you prefer to think in a different coordinate system, you can change the default twip-based coordinate system to inches, millimeters?even to custom units?by setting the Printer object’s ScaleMode property, just as you can for Forms and PictureBox controls. For the purposes of this solution I’ll use millimeters.
The Width and Height properties of the Printer object return page dimensions. These two properties always return the dimensions of the page in twips, regardless of the current coordinate system. The ScaleWidth and ScaleHeight properties return the page dimensions in the units of the current coordinate system?in this case, millimeters. Although most developers think the values are the same, the Width and Height properties generally slightly larger values than the ScaleWidth and ScaleHeight properties. The differences correspond to the width and height of the unprintable band (the area near the edges of the paper that most laser printers ignore). Assuming that the current coordinate system is twips (ScaleMode = 1), the Width and ScaleWidth properties for an A4 page have the following values:
Printer.ScaleWidth = 11222 Printer.Width = 11904
The difference, 682 twips, corresponds to approximately 0.47 inches. The width of the unprintable zone for the specific printer is approximately 0.24 inches, or 6 millimeters on either side. The height of this band is slightly smaller, 482 twips. In most situations you can ignore this band, because the user-specified margins typically exceed the unprintable band dimensions.
The current position on a form, printer, or PictureBox determines where the next graphics operation will start drawing. The CurrentX and CurrentY properties return or set the X and Y coordinates for the current position on an output device. When printing text, you first specify the coordinates of the first character’s upper left corner with these two properties and then print the string with the Print method. The current location is the position of the upper left corner of an imaginary rectangle that encloses the string.
The origin of the page is its upper left corner (0, 0) and coordinates increase to the right and down. However, you rarely want print on the entire page; typically, you want to print only within specified margins. Margins are an important aspect of a printout-just think of the last time you saw a printed page without a respectably sized margin. The margins, however, are not imposed by the printer or the graphics methods. It’s your responsibility to respect the page margins in effect and make sure that the printer refrains from drawing elements beyond the page’s margins. The left margin becomes the virtual left edge of the page, while the width of the printout is the page’s width minus the left and right margins.
Page orientation is also important. Portrait is the default orientation. When you switch to landscape orientation the page’s width and height are swapped. To change orientation programmatically, you can set the Printer object’s Orientation property to one of the constants cdlPortrait or cdlLandscape.
The Print Common Dialog Control
The Printer object always represents a specific printer, so output you generate through the Printer object gets directed to a specific printer and uses that printer’s settings. By default, the Printer object represents the printer designated as the default printer; but you can map the Printer object to any printer your computer can access, as well as change the printout’s properties through the Printer object. Typically, you’d leave the choice of which printer to use up to your users. The Common Dialog control of VB6 lets you display the Print common dialog control and read the settings selected by users. Although the Print dialog box was designed to simplify the task of customizing printout options, you can’t set the printout’s margins on this dialog box.
To use the Print common dialog box you must first add the Windows Common Dialog Box component to the Toolbox. To do that, right click on an empty area of the toolbox and select Components from the popup menu. Then, select the Microsoft Common Dialog Control 6.0 entry from the Components dialog box. To display the Print dialog, use statements such as the following:
CommonDialog1.CancelError = True On Error Resume Next ' display the dialog CommonDialog1.ShowPrinter If Err.Number = 32755 Then Exit Sub ' user cancelled If CommonDialog1.Orientation = cdlLandscape Then Printer.Orientation = cdlLandscape Else Printer.Orientation = cdlPortrait End If
The preceding code snippet displays the Print dialog then retrieves the orientation setting selected by the user and applies it to the current printer.
Note that setting the orientation on the Print dialog box doesn’t affect the printout in any way. You must read the setting and set the Printer object’s Orientation property accordingly. When you do so, widths and heights are swapped automatically. The constants cdlPortrait and cdlLandscape are available to your code automatically only after you’ve added the Windows Common Dialog control to the toolbox. Otherwise, you must declare the two constants and define them in your code, using the value 1 for cdlPortrait, and 2 for cdlLandscape.
VB6 has no tools to automatically generate previews, but you can emulate the printed page on a form quite easily, because both the Form object and the Printer object provide the same methods for generating graphics elements. Conveniently, that means you can use the same statements to create identical images on either an on-screen form or on a printed page. The printout on the form won’t be absolutely accurate though, because you can only emulate the printer’s behavior; the output doesn’t pass through the printer’s driver, so it’s not completely accurate. I’ll show you how to build tools to emulate the printer’s output on a form, but the preview may not always be identical to the actual printout. You can use these tools in your projects?and you’re welcome to improve them?but be aware of the limitations. You can preview a printout to verify that the output “looks” correct (see that the various elements are placed correctly on the page and that no text leaks into the margins), but don’t be surprised if the actual printout differs slightly, perhaps fitting an additional line on the printout page, as opposed to the preview form.
To simplify the switching between the Form preview object and the Printer object at run time, you can create an object variable and set it to the appropriate object from within your code. For example, to set the object to a form, you could write:
Dim PRN As Object Set PRN = Form1
Alternatively, you can set the PRN object to the Printer:
Set PRN = Printer
After that, you can create graphics elements by calling the appropriate methods on the PRN object, such as PRN.Print, PRN.Line, and so on. Of course, you give up the convenience of early binding, but you gain the advantage of having the same code work with both the Form and the Printer objects. Unfortunately, you can also write code such as PRN.BackColor, even if the PRN object refers to the Printer, which doesn’t provide a BackColor property, and thus would cause a run-time error. One compromise is to work with the Form object while writing and testing your code, and introduce the PRN object during the last stages of development. You should also avoid calling methods or properties of the Form object that are not supported by the Printer object. Of course, another way is to duplicate the code for both objects, and use early binding to catch those errors at compile time.
Simple Printing with VB6
With those basics in mind, you can start an exploration of the basic printing mechanisms of VB6 with a simple project that prints strings and lines. It’s the SimplePrinting project, whose main form is shown in Figure 4. The Preview Text and Print Text buttons generate the same output on the form and the printer respectively. They both call the CreateSimplePrintout() subroutine to print the text you see on the form in Figure 4.