In this section, you will learn
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.
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.
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.
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.
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.
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.
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.
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.
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.
As shown in Listing 21.3, you can use the CDC::GetDeviceCaps function to retrieve information about a selected output device.
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.
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.
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.
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);
}
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;
}
}
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.
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 Sections" );
// 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 );
}
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.
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.
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);
}
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?
2. Modify the MFCPrint project so that it prints the time printed at the top of each page.
Download Sample Programs - MFCPrint.zip
|
|
|
|