Teach Yourself Visual C++® 5 in 24 Hours

Previous chapterNext chapterContents


- Hour 21 -
Printing

There are two primary output devices in Windows: the screen and the printer. Printing using the MFC class library is much simpler than printing in a straight SDK and C environment.

In this hour, you will learn

You also will create a sample program to demonstrate how printing is done for a Document/View application.

What Is Printing in a Windows Program?

Programs written for Windows should be hardware independent. This extends to the printer, where all output is performed through device contexts, much as displays to the screen are done.

Many programs written for Windows need no hard copy output. However, many programs can benefit by providing reports or other information in a printout. The Document/View architecture and MFC class library provide standard printing functionality to all SDI and MDI applications.

Historically, printing in a program written for Windows has been a nightmare. Using the traditional SDK approach, seemingly dozens of function calls and structures must be used to send output to a printer. Because Windows supports literally hundreds of printers, en-suring that printed output is printed correctly can be difficult.

The Document/View architecture and the MFC class library help make creating hard-copy printouts in a Windows program much easier. You can use the Common Print dialog box and reuse view functions that are used to display information on the screen.

Printing in an MFC program is almost effortless. If your program uses the Document/View architecture and does all of its drawing in the OnDraw function, you might not need to do anything to get basic printing to work. The source code provided in Listing 21.1 is an example of a simple OnDraw function that can be used for screen and printer output.

TYPE: Listing 21.1. A simple OnDraw function that works for the screen and the printer.

void CPrintView::OnDraw(CDC* pDC)

{

    CString szMsg = "Hello printer and view example.";



    pDC->TextOut( 0, 50, szMsg );

} Using the view's OnDraw member function is an easy way to take advantage of the hardware independence offered by Windows. If your code is portable enough to run on a variety of screen displays, you probably will get an acceptable printout using most printers available for Windows.

On the other hand, there are many cases in which you might want to get more involved in the printing. For example, if your view is not WYSIWYG, the printed output might not be suitable. If your view is a form view, for example, you might want to print your document's data in another form, such as a list of items in the entire document or detailed information about an item in the current form.

When you customize the view functions that are responsible for printing, you can also offer nice user interface elements such as headers, footers, page numbers, or special fonts.

Understanding the MFC Printing Routines

The following lists the CView routines used to print a view:

These member functions are called by the MFC framework as the print routine progresses. The relationship between these routines is shown in Figure 21.1.

Figure 21.1.
CView member functions called while printing a document.

As shown in Figure 21.1, only the OnPrepareDC and OnPrint member functions are called for every page sent to the printer. The other functions are used to initiate variables in preparation of the printout or to clean up and free resources after the printout has been completed.

When AppWizard creates a view class for your program, the OnPreparePrinting, OnBeginPrinting, and OnEndPrinting functions are automatically provided for you. You can add the other member functions with ClassWizard if you must override the basic functionality.

Creating an MFC Printing Example

As an example of the MFC print functions, create a small program that displays information to the screen and the printer. To begin, create an SDI or MDI project named MFCPrint using ClassWizard.

With ClassWizard, add two message-handling functions for the CMFCPrintView class: OnPrepareDC and OnPrint. You'll find out more about OnPrepareDC and OnPrint in the next few sections. The other printing functions have already been included in the CMFCPrintView class by AppWizard.

Add five new member variables and two new functions to the implementation section of the CMFCPrintView class, as shown in Listing 21.2.

TYPE: Listing 21.2. New CPrintView member variables.

protected:

    int     m_nCurrentPrintedPage;

    CFont*  m_pFntBold;

    CFont*  m_pFntBanner;

    CFont*  m_pFntHighlight;

    CPen    m_penBlack;

    void PrintHeader(CDC* pDC);

void PrintFooter(CDC* pDC); These new member variables and functions are used during the printout.

Exploring the CPrintInfo Class

The CPrintInfo class is used to store information about the current state of a printout. A pointer to a CPrintInfo object is passed as a parameter to functions involved in the printout. You can access attributes of the CPrintInfo object for information about the printout, or in some cases you can change the attributes to customize the printout. Here are the most commonly used CPrintInfo members:

Some of these members are used in a particular function. As you learn about each function in the next few sections, commonly used CPrintInfo members will be discussed.

Using the OnPreparePrinting Function

AppWizard generates the OnPreparePrinting function for a project's initial view class. This function is called before the Common Print dialog box is displayed, and it gives you an opportunity to change the values displayed in the Print dialog box.

If your document has more than one page, you should calculate the number of pages, if possible. This allows the maximum number of pages to be displayed in the Print dialog box. You can set the number of pages by calling the CPrintInfo::SetMaxPages function:

pInfo->SetMaxPages( 2 );


CAUTION: You should not allocate resources in the CPrintInfo::SetMaxPages func-tion because you are not notified if the user cancels the Print dialog box.

Using the OnBeginPrinting Function

The OnBeginPrinting function is called after the user has pressed OK in the Print dialog box in order to start the printout. This function is the proper place to allocate resources such as fonts, brushes, and pens that might be needed for the printout. In the example you work with later, this function is used to create CFont objects.

This function is called only once for each printout. If this function is called, the OnEndPrinting function is called after the printout is finished in order to give you a chance to free resources allocated in the OnBeginPrinting function.

Using the OnPrepareDC Function

The OnPrepareDC function is called just before a page is printed or displayed in the view. If OnPrepareDC is called with the CPrintInfo pointer set to NULL, the document is not being printed.

This function often is overridden for multiple-page documents in order to continue the printout over multiple pages. To print another page, set the CPrintInfo::m_bContinue member variable to TRUE:

pInfo->m_bContinuePrinting = TRUE;


Time Saver: By default, only one page will be printed unless you override this function and set m_bContinuePrinting to TRUE.

Using the OnPrint Function

The OnPrint function is the printing counterpart to OnDraw. In fact, many programs can just use the default version of OnPrint, which calls OnDraw. However, most printouts can benefit from providing page numbers, headers, footers, or special fonts that aren't displayed in the view.

New Term: A twip is one-twentieth of a point. A point, in turn, is almost exactly 1/72 of an inch. This works out to about 1,440 twips per inch.

When printing, the MM_TWIPS mapping mode is used. The really odd thing about MM_TWIPS is that the mapping mode begins with the upper-left corner at (0,0) and runs in a negative direction down the page, making the point one inch below the origin (0,-1440). Like other modes, the mapping mode extends in a positive direction to the right side of the page.

The OnPrint function is called once for every page. If you're printing data that is arranged so that the page number can easily be determined, it's a good idea to use the CPrintInfo parameter to determine the current page number.


Just a Minute: Remember, the user might ask for a range of pages to be printed, not just the entire document.

Using the OnEndPrinting Function

The OnEndPrinting function is called after the printout is finished. This function can be called because the job was completed successfully or because it has failed; you don't really know. The purpose of this function is to release any resources that were allocated in the OnBeginPrinting function.

Querying the Printing Device Context

Unlike video displays, printing devices offer a wide variation in their capabilities. It's a good idea to examine the capabilities of a printout device before attempting graphics functions.

As shown in Listing 21.3, you can use the CDC::GetDeviceCaps function to retrieve information about a selected output device.

TYPE: Listing 21.3. Using GetDeviceCaps to determine whether BitBlt is supported.

int nRasterFlags = pDC->GetDeviceCaps(RASTERCAPS);

if(nRasterCaps & RC_BITBLT)

{

    // BitBlt is allowed

}

else

{

    // BitBlt is not allowed

} GetDeviceCaps accepts an index as a parameter. This index specifies the type of information returned from the function. In Listing 21.2, the RASTERCAPS index results in a return value that contains flags which indicate the raster capabilities of the device. If the RC_BITBLT flag is set, the BitBlt function can be applied to that device.


Time Saver: You can use this function for any type of device--not just printers. This function can be used to return all types of information. Check the online documentation for details.

Adding More Functionality to MFCPrint

The remaining part of this hour is used to add printing functionality to the MFCPrint example, using the functions discussed in the earlier sections. The OnPreparePrinting function supplied by AppWizard isn't changed for this example.

The CMFCPrintView Constructor and Destructor

The member variables added to the CMFCPrintView class must be initialized in the CMFCPrintView constructor, and any allocated resources must be released in the destructor. The source code for the constructor and destructor is provided in Listing 21.4.

TYPE: Listing 21.4. The constructor and destructor for CMFCPrintView.

CMFCPrintView::CMFCPrintView()

{

    COLORREF clrBlack = GetSysColor(COLOR_WINDOWFRAME);

    m_penBlack.CreatePen(PS_SOLID, 0, clrBlack);

    m_pFntBold = 0;

    m_pFntBanner = 0;

    m_pFntHighlight = 0;

}



CMFCPrintView::~CMFCPrintView()

{

    // The fonts must be released explicitly

    // since they were created with new.

    delete m_pFntBold;

    delete m_pFntBanner;

    delete m_pFntHighlight;

} The usual practice with GDI objects is to defer actually creating the object until it is needed. The constructor for CMFCPrintView sets each of the CFont pointer variables to 0; these objects are created on the heap when the print job begins.

The destructor for CMFCPrintView deletes the dynamically allocated CFont objects. Under normal execution, these pointers do not need to be freed because resources are released at the end of a print job. However, this code protects you in case of abnormal program termination, and because it is always safe to delete a pointer to NULL, no harm will come to your program in the normal case.

Allocating Resources in the OnBeginPrinting Function

As you learned earlier, the OnBeginPrinting function is called just before printing begins. Add the source code provided in Listing 21.5 to the OnBeginPrinting function. This version of OnBeginPrinting creates three new fonts that are used in the printout. (You learned about creating fonts in Hour 13, "Fonts.")


Just a Minute: To prevent compiler warnings about unused variables, AppWizard comments out the pDC and pInfo parameters. If you use these parameters, you must remove the comments, as shown in Listing 21.5.

TYPE: Listing 21.5. Allocating new fonts in the OnBeginPrinting function.

void CMFCPrintView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)

{

    ASSERT( m_pFntBold == 0 );

    ASSERT( m_pFntBanner == 0 );

    ASSERT( m_pFntHighlight == 0 );



    m_nCurrentPrintedPage = 0;

    pDC->SetMapMode( MM_TWIPS );



    // Create the bold font used for the fields. TimesRoman,

    // 12 point semi-bold is used.

    m_pFntBold = new CFont;

    ASSERT( m_pFntBold );

    m_pFntBold->CreateFont( -240,

                            0,

                            0,

                            0,

                            FW_SEMIBOLD,

                            FALSE,

                            FALSE,

                            0,

                            ANSI_CHARSET,

                            OUT_TT_PRECIS,

                            CLIP_DEFAULT_PRECIS,

                            DEFAULT_QUALITY,

                            DEFAULT_PITCH | FF_ROMAN,

                            "Times Roman" );

    // Create the normal font used for the Headline banner.

    // TimesRoman, 18 point italic is used.

    m_pFntBanner = new CFont;

    ASSERT( m_pFntBanner );

    m_pFntBanner->CreateFont( -360,

                              0,

                              0,

                              0,

                              FW_NORMAL,

                              TRUE,

                              FALSE,

                              0,

                              ANSI_CHARSET,

                              OUT_TT_PRECIS,

                              CLIP_DEFAULT_PRECIS,

                              DEFAULT_QUALITY,

                              DEFAULT_PITCH | FF_ROMAN,

                              "Times Roman" );

    // Create the normal font used for the Headline highlight.

    // This is the text used under the headline banner, and in

    // the footer. TimesRoman, 8 point is used.

    m_pFntHighlight = new CFont;

    ASSERT( m_pFntHighlight );

    m_pFntHighlight->CreateFont( -160,

                                 0,

                                 0,

                                 0,

                                 FW_NORMAL,

                                 TRUE,

                                 FALSE,

                                 0,

                                 ANSI_CHARSET,

                                 OUT_TT_PRECIS,

                                 CLIP_DEFAULT_PRECIS,

                                 DEFAULT_QUALITY,

                                 DEFAULT_PITCH | FF_ROMAN,

                                 "Times Roman" );

    CView::OnBeginPrinting(pDC, pInfo);

}

Handling Multiple Pages in the OnPrepareDC Function

The OnPrepareDC function is called just before each page is printed. The default version of this function allows one page to be printed. By modifying the bContinuePrinting flag, you can use this function to continue the printout. Add the source code provided in Listing 21.6 to the OnPrepareDC function.

TYPE: Listing 21.6. The OnPrepareDC function.



void CMFCPrintView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)

{

    CView::OnPrepareDC(pDC, pInfo);

    if( pInfo )

    {

        if( pInfo->m_nCurPage < 3 )

            pInfo->m_bContinuePrinting = TRUE;

        else

            pInfo->m_bContinuePrinting = FALSE;

    }

}

Modifying the MFCPrint OnPrint Function

The default implementation of OnPrint calls the OnDraw member function. For this example, add the source code from Listing 21.7 to OnPrint, which sends a header followed by several rows of text and a footer to the printer.

TYPE: Listing 21.7. Printing a header and text using the OnPrint function.

void CMFCPrintView::OnPrint(CDC* pDC, CPrintInfo* pInfo)

{

    CPoint      pt( 5000, -7000 );

    TEXTMETRIC  tm;



    //Since the DC has been modified, it's always a good idea to reset

    //the mapping mode, no matter which one you use. In our case, since

    //we use MM_TWIPS, we have to reset the mapping mode for each page.

    pDC->SetMapMode( MM_TWIPS );

    PrintHeader( pDC );

    CFont* pOldFont = pDC->SelectObject( m_pFntBold );

    pDC->GetTextMetrics( &tm );

    int cyText = tm.tmHeight + tm.tmExternalLeading;



    m_nCurrentPrintedPage++;

    pDC->TextOut( pt.x, pt.y, "Hello Printer!!!" );



    pt.y += cyText;

    CString  szPageInfo;

    szPageInfo.Format( TEXT("Page number %d"),

                       m_nCurrentPrintedPage );

    pDC->TextOut( pt.x, pt.y, szPageInfo );



    pDC->SelectObject( pOldFont );

    PrintFooter( pDC );

} 

Listing 21.8 provides the source code used to print the header and footer. Add these two functions to the MFCPrintView.cpp source file.

TYPE: Listing 21.8. Printing the header and footer.

void CMFCPrintView::PrintFooter( CDC* pDC )

{

    ASSERT( pDC );

    TEXTMETRIC  tm;

    CPoint  pt( 0, -14400 );



    //Select the smaller font used for the file name.

    ASSERT( m_pFntHighlight );

    CFont* pOldFont = pDC->SelectObject( m_pFntHighlight );

    ASSERT( pOldFont );

    pDC->GetTextMetrics( &tm );

    int cyText = tm.tmHeight + tm.tmExternalLeading;



    // Print the underline bar. This is the same pen used to draw

    // black lines in the control. 10000 twips is about 7 inches or so.

    CPen* pOldPen = pDC->SelectObject( &m_penBlack );

    ASSERT( pOldPen );

    pt.y -= (cyText / 2);

    pDC->MoveTo( pt );

    pDC->LineTo( 10000, pt.y );



    pt.y -= cyText;

    pDC->TextOut( pt.x, pt.y, TEXT("Every page needs a footer") );

    // Restore GDI objects.

    pDC->SelectObject( pOldFont );

    pDC->SelectObject( pOldPen );

}

void CMFCPrintView::PrintHeader( CDC* pDC )

{

    ASSERT( pDC );

    TEXTMETRIC  tm;

    CPoint      pt( 0, 0 );



    // Select the banner font, and print the headline.

    CFont* pOldFont = pDC->SelectObject( m_pFntBanner );

    ASSERT( pOldFont );

    pDC->GetTextMetrics( &tm );

    int cyText = tm.tmHeight + tm.tmExternalLeading;

    pt.y -= cyText;

    pDC->TextOut( pt.x, pt.y, " Teach Yourself Visual C++ in 24 Hours" );

    // Move down one line, and print and underline bar. This is the same

    // pen used to draw black lines in the control. 10000 twips is about

    // 7 inches or so.

    CPen* pOldPen = pDC->SelectObject( &m_penBlack );

    ASSERT( pOldPen );

    pt.y -= cyText;

    pDC->MoveTo( pt );

    pDC->LineTo( 10000, pt.y );

    // We move down about 1/2 line, and print the report type using the

    // smaller font.

    VERIFY( pDC->SelectObject( m_pFntHighlight ) );

    pDC->GetTextMetrics( &tm );

    cyText = tm.tmHeight + tm.tmExternalLeading;

    pt.y -= (cyText / 2);

    pDC->TextOut( pt.x, pt.y, "Printing Demonstration" );

    // Restore GDI objects.

    pDC->SelectObject( pOldFont );

    pDC->SelectObject( pOldPen );

}

Using the OnEndPrinting Function to Release Resources

The OnEndPrinting function is called once per print job, but only if the OnBeginPrinting function has been called. Use this function to release the resources allocated in OnBeginPrinting.


CAUTION: You must match all of your allocations made in OnBeginPrinting with deallocations in OnEndPrinting. If you don't, you will get a memory or resource leak.

Listing 21.9 provides the source code for the OnEndPrinting function used in MFCPrintView. As in the OnBeginPrinting function presented in Listing 21.5, AppWizard comments out the pDC and pInfo parameters. If you use these parameters, you must remove the comments.

TYPE: Listing 21.9. Releasing resources in the OnEndPrinting function.

void CMFCPrintView::OnEndPrinting(CDC* pDC, CPrintInfo* pInfo)

{

    delete m_pFntBold;

    delete m_pFntBanner;

    delete m_pFntHighlight;

    // Since the destructor also deletes these fonts, we have

    // to set pointers to 0 to avoid dangling pointers and exceptions

    // generated by invoking delete on a non-valid pointer.

    m_pFntBold = 0;

    m_pFntBanner = 0;

    m_pFntHighlight = 0;

    CView::OnEndPrinting(pDC, pInfo);

} 

Compile and run the Print project, and send the output to the printer using either the File menu or the toolbar icon. Send the sample printout pages to the printer.

Summary

In this hour you learned about the print functions and support offered by MFC and the Document/View architecture. You also created a small sample program that sent three pages of text to the printer.

Q&A

Q How can I draw graphics such as rectangles and ellipses on my printouts?

A The same way that you draw them to the screen--you can use all the basic GDI functions when printing; this includes Ellipse and Rectangle.

Q How can I change my printout to have landscape instead of portrait orientation?

A To change the page orientation to landscape, you must change a printing attribute attached to the device context. Due to minor differences in the way in which Windows 95 and Windows NT handle printing details, this must be done for each page during the OnPrepareDC function. Add the following code at the top of CMFCPrintView::OnPrepareDC:
if(pDC->IsPrinting())

{

    LPDEVMODE  pDevMode;

    pDevMode = pInfo->m_pPD->GetDevMode();

    pDevMode->dmOrientation = DMORIENT_LANDSCAPE;

    pDC->ResetDC(pDevMode);

}

Workshop

The Workshop is designed to help you anticipate possible questions, review what you've learned, and begin thinking ahead to putting your knowledge into practice. The answers to the quiz are in Appendix B, "Quiz Answers."

Quiz

1. How can you determine whether a printer supports BitBlt operations?

2. What are the five MFC view functions that are most commonly overridden for printing?

3. Which MFC view functions are called once for every printed page, and which functions are called once per print job?

4. What class is used to store information about the state of a print job?

5. Which view function is used to allocate resources used to render the printout?

6. Approximately how many twips are in an inch?

7. What CPrintInfo member variable must be set for multiple page printouts?

8. When using the MM_TWIPS mapping mode, which direction is positive: up or down?

9. When using the MM_TWIPS mapping mode, which direction is positive: left or right?

10. Which MFC view function should be used to release resources allocated for printing?

Exercises

1. Modify the MFCPrint project so that it prints the page number at the foot of each page.

2. Modify the MFCPrint project so that it prints the time printed at the top of each page.


Previous chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.