This is a continuation from the previous module... Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:
Printing and Print Preview
If you're depending on the Win32 API alone, printing is one of the tougher programming jobs you'll have. If you don't believe me, just skim through the 60-page module "Using the Printer" in Charles Petzold's Programming Windows 95 (Microsoft Press, 1996). Other books about Microsoft Windows ignore the subject completely. The Microsoft Foundation Class (MFC) Library version 6.0 application framework goes a long way toward making printing easy. As a bonus, it adds a print preview capability that behaves like the print preview functions in commercial Windows-based programs such as Microsoft Word and Microsoft Excel.
In this module, you'll learn how to use the MFC library Print and Print Preview features. In the process, you'll get a feeling for what's involved in Windows printing and how it's different from printing in MS-DOS. First you'll do some What You See Is What You Get - WYSIWYG printing, in which the printer output matches the screen display. This option requires careful use of mapping modes. Later you'll print a paginated data processing-style report that doesn't reflect the screen display at all. In that example, you will use a template array to structure your document so that the program can print any specified range of pages on demand.
In the old days, programmers had to worry about configuring their applications for dozens of printers. Now Windows makes life easy because it provides all of the printer drivers you'll ever need. It also supplies a consistent user interface for printing.
Standard Printer Dialogs
When the user chooses Print from the File menu of a Windows-based application, the standard Print dialog appears, as shown in Figure 2.
Figure 1: Standard Print, Print Preview and Print Setup sub menus.
Figure 2: The standard Print dialog.
If the user chooses Print Setup from the File menu, the standard Print Setup dialog appears, as shown in Figure 3.
Figure 3: The standard Print Setup dialog.
During the printing process, the application displays a standard printer status dialog, as shown in Figure 4.
Figure 4: The standard printer status dialog.
Interactive Print Page Selection
If you've worked in the data processing field, you might be used to batch-mode printing. A program reads a record and then formats and prints selected information as a line in a report. Let's say, for example, that every time 50 lines have been printed the program ejects the paper and prints a new page heading. The programmer assumes that the whole report will be printed at one time and makes no allowance for interactively printing selected pages.
As Figure 19-1 shows, page numbers are important in Windows-based printing. A program must respond to a user's page selection by calculating which information to print and then printing the selected pages. If you're aware of this page selection requirement, you can design your application's data structures accordingly.
Remember the student list from Module 11? What if the list included 1000 students' names and the user wanted to print page 5 of a student report? If you assumed that each student record required one print line and that a page held 50 lines, page 5 would include records 201 through 250. With an MFC list collection class, you're stuck iterating through the first 200 list elements before you can start printing. Maybe the list isn't the ideal data structure. How about an array collection instead? With the CObArray class (or with one of the template array classes), you can directly access the 201st student record. Not every application has elements that map to a fixed number of print lines. Suppose the student record contained a multi-line text biography field. Because you wouldn't know how many biography lines each record included, you'd have to search through the whole file to determine the page breaks. If your program could remember those page breaks as it calculated them, its efficiency would increase.
Display Pages vs. Printed Pages
In many cases, you'll want a printed page to correspond to a display page. As you learned in Module 4, you cannot guarantee that objects will be printed exactly as they are displayed on screen. With TrueType fonts, however, your printed page will be pretty close. If you're working with full-size paper and you want the corresponding display to be readable, you'll certainly want a display window that is larger than the screen. Thus, a scrolling view such as the one that the CScrollView class provides is ideal for your printable views.
Sometimes, however, you might not care about display pages. Perhaps your view holds its data in a list box, or maybe you don't need to display the data at all. In these cases, your program can contain stand-alone print logic that simply extracts data from the document and sends it to the printer. Of course, the program must properly respond to a user's page-range request. If you query the printer to determine the paper size and orientation (portrait or landscape), you can adjust the pagination accordingly.
The MFC library Print Preview feature shows you on screen the exact page and line breaks you'll get when you print your document on a selected printer. The fonts might look a little funny, especially in the smaller sizes, but that's not a problem. Look now at the print preview window that appears in "The MYMFC19 Example - A WYSIWYG Print Program".
Print Preview is an MFC library feature, not a Windows feature. Don't underestimate how much effort went into programming Print Preview. The Print Preview program examines each character individually, determining its position based on the printer's device context. After selecting an approximating font, the program displays the character in the print preview window at the proper location.
Programming for the Printer
The application framework does most of the work for printing and print preview. To use the printer effectively, you must understand the sequence of function calls and know which functions to override.
The Printer Device Context and the CView::OnDraw Function
When your program prints on the printer, it uses a device context object of class CDC. Don't worry about where the object comes from; the application framework constructs it and passes it as a parameter to your view's OnDraw() function. If your application uses the printer to duplicate the display, the OnDraw() function can do double duty. If you're displaying, the OnPaint() function calls OnDraw() and the device context is the display context. If you're printing, OnDraw() is called by another CView virtual function, OnPrint(), with a printer device context as a parameter. The OnPrint() function is called once to print an entire page. In print preview mode, the OnDraw() parameter is actually a pointer to a CPreviewDC object. Your OnPrint() and OnDraw() functions work the same regardless of whether you're printing or previewing.
The CView::OnPrint Function
You've seen that the base class OnPrint() function calls OnDraw() and that OnDraw() can use both a display device context and a printer device context. The mapping mode should be set before OnPrint() is called. You can override OnPrint() to print items that you don't need on the display, such as a title page, headers, and footers. The OnPrint() parameters are as follows:
A pointer to the device context.
A pointer to a print information object (CPrintInfo) that includes page dimensions, the current page number, and the maximum page number.
In your overridden OnPrint() function, you can elect not to call OnDraw() at all to support print logic that is totally independent of the display logic. The application framework calls the OnPrint() function once for each page to be printed, with the current page number in the CPrintInfo structure. You'll soon find out how the application framework determines the page number.
Preparing the Device Context: The CView::OnPrepareDC Function
If you need a display mapping mode other than MM_TEXT (and you often do), that mode is usually set in the view's OnPrepareDC() function. You override this function yourself if your view class is derived directly from CView, but it's already overridden if your view is derived from CScrollView. The OnPrepareDC() function is called in OnPaint() immediately before the call to OnDraw(). If you're printing, the same OnPrepareDC() function is called, this time immediately before the application framework calls OnPrint(). Thus, the mapping mode is set before both the painting of the view and the printing of a page.
The second parameter of the OnPrepareDC() function is a pointer to a CPrintInfo structure. This pointer is valid only if OnPrepareDC() is being called prior to printing. You can test for this condition by calling the CDC member function IsPrinting(). The IsPrinting() function is particularly handy if you're using OnPrepareDC() to set different mapping modes for the display and the printer. If you do not know in advance how many pages your print job requires, your overridden OnPrepareDC() function can detect the end of the document and reset the m_bContinuePrinting flag in the CPrintInfo structure. When this flag is FALSE, the OnPrint() function won't be called again and control will pass to the end of the print loop.
The Start and End of a Print Job
When a print job starts, the application framework calls two CView functions, OnPreparePrinting() and OnBeginPrinting(). (AppWizard generates the OnPreparePrinting(), OnBeginPrinting(), and OnEndPrinting() functions for you if you select the Printing And Print Preview option.) The first function, OnPreparePrinting(), is called before the display of the Print dialog. If you know the first and last page numbers, call CPrintInfo::SetMinPage and CPrintInfo::SetMaxPage in OnPreparePrinting(). The page numbers you pass to these functions will appear in the Print dialog for the user to override.
The second function, OnBeginPrinting(), is called after the Print dialog exits. Override this function to create Graphics Device Interface (GDI) objects, such as fonts, that you need for the entire print job. A program runs faster if you create a font once instead of re-creating it for each page. The CView function OnEndPrinting() is called at the end of the print job, after the last page has been printed. Override this function to get rid of GDI objects created in OnBeginPrinting(). The following table summarizes the important overridable CView print loop functions.
Common Override Behavior
Sets first and last page numbers.
Creates GDI objects.
OnPrepareDC() (for each page)
Sets mapping mode and optionally detects end of print job.
Does print-specific output and then calls OnDraw() (for each page)
Deletes GDI objects.
The MYMFC19 Project Example: A WYSIWYG Print Program
This example displays and prints a single page of text stored in a document. The printed image should match the displayed image. The MM_TWIPS mapping mode is used for both printer and display. First we'll use a fixed drawing rectangle; later we'll base the drawing rectangle on the printable area rectangle supplied by the printer driver.
Here are the steps for building the example:
Run AppWizard to generate \mfcproject\mymfc19 MDI project. Accept the default options, and for step 6, rename the document and view classes as shown in Table 2, select the CScrollView as the view base class and files as shown here.
Source Code File
Table 2: New name for the view and document class files.
Figure 5: AppWizard step 6 of 6, renaming the files and selecting CScrollView as a view base class.
Figure 6: Renaming the document files and class.
Figure 7: MYMFC19 project summary.
Note that this is an MDI application. Add a CStringArray data member to the CPoemDoc class. Edit the PoemDoc.h header file or use ClassView.
Figure 8: Adding member variable context menu.
Figure 9: Adding a CStringArray data member to the CPoemDoc class.
The document data is stored in a string array. The MFC library CStringArray class holds an array of CString objects, accessible by a zero-based subscript. You need not set a maximum dimension in the declaration because the array is dynamic.
Add a CRect data member to the CStringView class. Edit the StringView.h header file or use ClassView:
Figure 10: Adding another member variable.
Figure 11: Adding a CRect data member to the CStringView class.
Edit three CPoemDoc member functions in the file PoemDoc.cpp. AppWizard generated skeleton OnNewDocument() and Serialize() functions, but we'll have to use ClassWizard to override the DeleteContents() function. We'll initialize the poem document in the overridden OnNewDocument() function. DeleteContents() is called in CDocument::OnNewDocument, so by calling the base class function first we're sure the poem won't be deleted. The text, by the way, is an excerpt from the twentieth poem in Lawrence Ferlinghetti's book A Coney Island of the Mind. Type 10 lines of your choice. You can substitute another poem or maybe your favorite Win32 function description. Add the following code:
m_stringArray = "The pennycandystore beyond the El";
m_stringArray = "is where I first";
m_stringArray = " fell in love";
m_stringArray = " with unreality";
m_stringArray = "Jellybeans glowed in the semi-gloom";
m_stringArray = "of that september afternoon";
m_stringArray = "A cat upon the counter moved among";
m_stringArray = " the licorice sticks";
m_stringArray = " and tootsie rolls";
m_stringArray = " and Oh Boy Gum";
The CStringArray class supports dynamic arrays, but here we're using the m_stringArray object as though it were a static array of 10 elements. The application framework calls the document's virtual DeleteContents() function when it closes the document; this action deletes the strings in the array. A CStringArray contains actual objects, and a CObArray contains pointers to objects. This distinction is important when it's time to delete the array elements. Here the RemoveAll() function actually deletes the string objects:
// called before OnNewDocument() and when document is closed
Figure 12: Adding the RemoveAll() function to the document class.
Serialization isn't important in this example, but the following function illustrates how easy it is to serialize strings. The application framework calls the DeleteContents() function before loading from the archive, so you don't have to worry about emptying the array. Add the following boldface code:
void CPoemDoc::Serialize(CArchive& ar)
Edit the OnInitialUpdate() function in StringView.cpp. You must override the function for all classes derived from CScrollView. This function's job is to set the logical window size and the mapping mode. Add the following code:
CSize sizeTotal(m_rectPrint.Width(), -m_rectPrint.Height());
CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2); // page scroll
CSize sizeLine(sizeTotal.cx / 100, sizeTotal.cy / 100); // line scroll
SetScrollSizes(MM_TWIPS, sizeTotal, sizePage, sizeLine);
Edit the OnDraw() function in StringView.cpp. The OnDraw() function of class CStringView draws on both the display and the printer. In addition to displaying the poem text lines in 10-point roman font, it draws a border around the printable area and a crude ruler along the top and left margins. OnDraw() assumes the MM_TWIPS mapping mode, in which 1 inch = 1440 units. Add the boldface code shown below.
void CStringView::OnDraw(CDC* pDC)
int i, j, nHeight;
CPoemDoc* pDoc = GetDocument();
// Draw a border — slightly smaller to avoid truncation
pDC->Rectangle(m_rectPrint + CRect(0, 0, -20, 20));
// Draw horizontal and vertical rulers
j = m_rectPrint.Width() / 1440;
for (i = 0; i <= j; i++)
pDC->TextOut(i * 1440, 0, str);
j = -(m_rectPrint.Height() / 1440);
for (i = 0; i <= j; i++)
pDC->TextOut(0, -i * 1440, str);
// Print the poem 0.5 inch down and over;
// use 10-point roman font
font.CreateFont(-200, 0, 0, 0, 400, FALSE, FALSE, 0, ANSI_CHARSET,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,
"Times New Roman");
CFont* pOldFont = (CFont*) pDC->SelectObject(&font);
nHeight = tm.tmHeight + tm.tmExternalLeading;
TRACE("font height = %d, internal leading = %d\n", nHeight, tm.tmInternalLeading);
j = pDoc->m_stringArray.GetSize();
for (i = 0; i < j; i++)
pDC->TextOut(720, -i * nHeight - 720, pDoc->m_stringArray[i]);
TRACE("LOGPIXELSX = %d, LOGPIXELSY = %d\n", pDC->GetDeviceCaps(LOGPIXELSX),
TRACE("HORZSIZE = %d, VERTSIZE = %d\n", pDC->GetDeviceCaps(HORZSIZE),
Edit the OnPreparePrinting() function in StringView.cpp. This function sets the maximum number of pages in the print job. This example has only one page. It's absolutely necessary to call the base class DoPreparePrinting() function in your overridden OnPreparePrinting() function. Add the following code:
BOOL CStringView::OnPreparePrinting(CPrintInfo* pInfo)
Edit the constructor in StringView.cpp. The initial value of the print rectangle should be 8-by-15 inches, expressed in twips (1 inch = 1440 twips). Add the following boldface code:
CStringView::CStringView() : m_rectPrint(0, 0, 11520, -15120)
Build and test the application. If you run the MYMFC19 application under Microsoft Windows NT with the lowest screen resolution, your MDI child window will look like the one shown below. The text will be larger under higher resolutions and under Windows 95 and Windows 98.
Figure 13: MYMFC19 program output.
The window text is too small, isn't it? Go ahead and choose Print Preview from the File menu, and then click twice with the magnifying glass to enlarge the image. The print preview output is illustrated here.
Figure 14: MYMFC19 program output when activating the Print Preview menu.
Continue on next module...part 2.
Further reading and digging:
MSDN MFC 9.0 class library online documentation - latest version.