img lemonade

Printing with Documents and Views

发表于2001/5/28 20:01:00  1294人阅读

分类: MFC

Printing with Documents and Views

MFC's print architecture is built around a kernel formed by GDI printing functions and virtual CView member functions. To understand what's on the outside, it helps to first understand what's on the inside. Here's the approach we'll take in this chapter as we study the code that enables an MFC application to support printing and print previewing:

  • Look at the Windows printing model, and examine the steps an SDK-style application goes through to print a document.

  • Understand the relationship between the Windows print architecture and the MFC print architecture and the mechanics of printing from MFC applications.

  • Develop a bare-bones printing program that demonstrates how the same code can be used to send output to either the screen or the printer.

  • Develop a more ambitious printing program whose printing and previewing capabilities are on a par with those of commercial applications.

As you'll discover, printing from an MFC application isn't altogether different than rendering to the screen except for the fact that printed output must be paginated. Because MFC handles almost everything else, much of the effort you expend writing printing code will be devoted to figuring out where the page breaks go and how to position your output on the printed page.

The Windows Print Architecture

Printing a document from a Windows application without the benefit of MFC involves a number of steps. You begin by obtaining a device context (DC) for the printer that output will go to. Just as an application needs a screen DC to send output to the screen, it needs a printer DC to send output to a printer. If you know the device name of the printer you want to print to, you can create a device context yourself with the Win32 ::CreateDC function or MFC's CDC::CreateDC:

CDC dc;
dc.CreateDC (NULL, _T ("HP LaserJet IIP"), NULL, NULL);

If you don't know the device name but would like the application to print to the default printer, you can use MFC's handy CPrintDialog::GetDefaults and CPrintDialog::GetPrinterDC functions to create the device context:

CDC dc;
CPrintDialog dlg (FALSE);
dlg.GetDefaults ();
dc.Attach (dlg.GetPrinterDC ());

If you'd like to let the user select a printer, you can use CPrintDialog::DoModal to display a Print dialog (one of the common dialogs supplied by the operating system) and call CPrintDialog::GetPrinterDC to get a DC after the dialog is dismissed:

CDC dc;
CPrintDialog dlg (FALSE);
if (dlg.DoModal () == IDOK)
	dc.Attach (dlg.GetPrinterDC ());    

To prevent resource leakage, you should delete a printer DC obtained by any of these methods when it's no longer needed. If the CDC object to which you attach the DC is created on the stack, deletion is automatic.

Once you have a printer DC in hand, you're ready to begin printing. The next step is to call ::StartDoc or its MFC equivalent, CDC::StartDoc, to mark the beginning of the print job. CDC::StartDoc accepts just one parameter: a pointer to a DOCINFO structure containing a descriptive name for the document that's about to be printed, the name of the file the output will go to if you're printing to a file rather than a printer, and other information about the print job. The statements

::ZeroMemory (&di, sizeof (DOCINFO));
di.cbSize = sizeof (DOCINFO);
di.lpszDocName = _T ("Budget Figures for the Current Fiscal Year");
dc.StartDoc (&di);

start a print job on the printer associated with the CDC object dc. If you open a printer window while the document is printing, the string "Budget Figures for the Current Fiscal Year" will identify the print job. If StartDoc fails, it returns a 0 or a less-than-0 value. If it succeeds, it returns a positive integer that equals the print job ID. You can use the print job ID in conjunction with Win32 print control functions such as ::GetJob and ::SetJob.

Next comes output to the page. Text and graphics are rendered on a printer with GDI functions. If dc refers to a screen device context, the statement

dc.Ellipse (0, 0, 100, 100);

draws an ellipse 100 logical units wide and 100 logical units high on the screen. If dc refers to a printer device context, the circle is drawn to the printer instead. Pages of output are framed between calls to CDC::StartPage and CDC::EndPage, which mark the beginning and end of each page. A document that contains nPageCount pages of output could be printed as follows:

for (int i=1; i<=nPageCount; i++) {
    dc.StartPage ();
    // Print page i
    dc.EndPage ();

In a simplified sense, calling EndPage is analogous to outputting a form feed character to the printer. In between StartPage and EndPage, you print the page by calling CDC member functions. Your application should call StartPage and EndPage even if the document contains only one page.

A common mistake that programmers make the first time they write printing code is failing to initialize the printer DC for each page. In Windows 95 and Windows 98, the device context's default attributes are restored each time StartPage is called. You can't just select a font or set the mapping mode right after the DC is created and expect those attributes to remain in effect indefinitely as you can for a screen DC. Instead, you must reinitialize the printer DC for each page. (In Microsoft Windows NT 3.5 and later, a printer DC retains its settings across calls to StartPage and EndPage, but even a Windows NT application should reinitialize the device context at the beginning of each page if it's to work under Windows 95 and Windows 98, too.) If you print using the MM_LOENGLISH mapping mode, for example, you should call CDC::SetMapMode at the beginning of each page, like this:

for (int i=1; i<=nPageCount; i++) {
    dc.StartPage ();
    dc.SetMapMode (MM_LOENGLISH);
    // Print page i.
    dc.EndPage ();

If you do it this way instead:

dc.SetMapMode (MM_LOENGLISH);
for (int i=1; i<=nPageCount; i++) {
    dc.StartPage ();
    // Print page i.
    dc.EndPage ();

printing will be performed in the default MM_TEXT mapping mode.

After it prints the final page, an application terminates a print job by calling CDC::EndDoc. Printing is made slightly more complicated by the fact that EndDoc shouldn't be called if a previous call to EndPage returned a code indicating that the print job had already been terminated by the GDI. EndPage returns a signed integer value greater than 0 if the page was successfully output to the printer. A 0 or negative return value indicates either that an error occurred or that the user canceled the print job while the page was being printed. In either of those two events, the return code will equal one of the following values.

Return Code Description
SP_ERROR The print job was aborted for an unspecified reason.
SP_APPABORT The print job was aborted because the user clicked the Cancel button in the dialog box that displays the status of the print job.
SP_USERABORT The print job was aborted because the user canceled it through the operating system shell.
SP_OUTOFDISK The system is out of disk space, so no further printer data can be spooled.
SP_OUTOFMEMORY The system is out of memory, so no further printer data can be spooled.

The following loop prints each page of a document and calls EndDoc at the end of the print job if and only if each page was successfully printed:

if (dc.StartDoc (&di) > 0) {
    BOOL bContinue = TRUE;

    for (int i=1; i<=nPageCount && bContinue; i++) {
        dc.StartPage ();
        // Initialize the device context.
        // Print page i.
        if (dc.EndPage () <= 0)
            bContinue = FALSE;

    if (bContinue)
        dc.EndDoc ();
        dc.AbortDoc ();

CDC::AbortDoc signals the end of an uncompleted print job just as EndDoc signals the end of a successful print job. AbortDoc can also be called between calls to StartPage and EndPage to terminate a print job before the final page is printed.

The Abort Procedure and the Abort Dialog

If that's all there was to sending output to a printer under Windows, printing wouldn't be such a formidable task after all. But there's more. Because a large print job can take minutes or even hours to complete, the user should be able to terminate a print job before it's finished. Windows applications traditionally give the user the means to cancel a print job by displaying a print status dialog containing a Cancel button. Clicking the Cancel button cancels printing by forcing EndPage to return SP_APPABORT. The mechanism that links the Cancel button to the printing code in your application is a function that Windows calls an abort procedure.

An abort procedure is an exported callback function that Windows calls repeatedly as it processes printed output. It's prototyped as follows:

BOOL CALLBACK AbortProc (HDC hDC, int nCode)

hDC holds the handle of the printer device context. nCode is 0 if printing is proceeding smoothly or SP_OUTOFDISK if the print spooler is temporarily out of disk space. nCode is usually ignored because the print spooler responds to an SP_OUTOFDISK condition by waiting around for more disk space to come free. The abort procedure's job is twofold:

  • To check the message queue with ::PeekMessage and retrieve and dispatch any waiting messages

  • To tell Windows whether printing should continue by returning TRUE (to continue printing) or FALSE (to abort)

You pass Windows the address of your abort procedure by calling ::SetAbortProc or CDC::SetAbortProc. A very simple abort procedure looks like this:

BOOL CALLBACK AbortProc (HDC hDC, int nCode)
    MSG msg;
    while (::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
        AfxGetThread ()->PumpMessage ();
    return TRUE;

The message loop inside AbortProc allows the WM_COMMAND message generated when the print status dialog's Cancel button is clicked to make it through to the window procedure even though the application is busy printing. In 16-bit Windows, the message loop plays an important role in multitasking by yielding so that the print spooler and other processes running in the system can get CPU time. In Windows 95 and Windows 98, yielding in the abort procedure enhances multitasking performance when 32-bit applications print to 16-bit printer drivers by reducing contention for the Win16Mutex—an internal flag that locks 32-bit applications out of the 16-bit kernel while a 16-bit application executes code there.

Before it begins printing (before calling StartDoc), the application calls SetAbortProc to set the abort procedure, disables its own window by calling CWnd::EnableWindow with a FALSE parameter, and displays the print status or "abort" dialog—a modeless dialog box containing a Cancel button and usually one or more static controls listing the document's file name and the number of the page that's currently being printed. Disabling the main window ensures that no other input will interrupt the printing process. The window is reenabled when printing is finished and the dialog box is destroyed. The dialog, meanwhile, sets a flag—call it bUserAbort—from FALSE to TRUE if the Cancel button is clicked. And the abort procedure is modified so that it returns FALSE to shut down printing if bUserAbort is TRUE.

BOOL CALLBACK AbortProc (HDC hDC, int nCode)
    MSG msg;
    while (!bUserAbort &&
        ::PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
        AfxGetThread ()->PumpMessage ();
    return !bUserAbort;

Thus, printing proceeds unimpeded if the Cancel button isn't clicked because AbortProc always returns a nonzero value. But if Cancel is clicked, bUserAbort changes from FALSE to TRUE, the next call to AbortProc returns 0, and Windows terminates the printing process. EndPage returns SP_APPABORT, and the call to EndDoc is subsequently bypassed.

Print Spooling

Everything I've described up to this point constitutes the "front end" of the printing process—the part the application is responsible for. Windows handles the back end, which is a joint effort on the part of the GDI, the print spooler, the printer driver, and other components of the 32-bit print subsystem. Windows supports two kinds of print spooling: EMF (enhanced metafile) print spooling and "raw" print spooling. If EMF print spooling is enabled, GDI calls executed through the printer DC are written to an enhanced metafile on the hard disk and stored there until the print spooler, which runs in a separate thread, unspools the commands and "plays" them into the printer driver. If raw print spooling (the only option available on PostScript printers) is selected instead, output is processed through the printer driver and spooled to disk in raw form. Spooling can also be disabled. In that case, GDI commands are transmitted directly to the printer driver each time EndPage is called. Print spooling speeds the return-to-application time by preventing a program from having to wait for the printer to physically print each page of output. Spooling metafile commands instead of raw printer data further improves the return-to-application time by decoupling the performance of the application from the performance of the printer driver.

Fortunately, applications can safely ignore what happens at the back end of the printing process and concentrate on the front end. Still, many details must be attended to before an application can get down to the real business of printing—paginating the output and executing GDI calls between StartPage and EndPage to render each page on the printer, for example. With this background in mind, let's see what MFC can do to help.

The MFC Print Architecture

MFC's simplified print architecture is just one more reason that Windows programmers are migrating away from the SDK and toward object-oriented development environments. When you add print capabilities to a document/view application, you can forget about most of the code samples in the previous section. The framework creates a printer DC for you and deletes the DC when printing is finished. The framework also calls StartDoc and EndDoc to begin and end the print job and StartPage and EndPage to bracket GDI calls for individual pages. The framework even supplies the dialog box that displays the status of the print job and the abort procedure that shuts down the print operation if the user clicks the Cancel button. And in some cases, the same OnDraw function that renders a document on the screen can render it on the printer and in a print preview window, too.

The key to printing from a document/view application is a set of virtual CView functions the framework calls at various stages during the printing process. These functions are listed in the following table. Which of them you override and what you do in the overrides depend on the content of your printed output. At the very least, you'll always override OnPreparePrinting and call DoPreparePrinting so that the framework will display a Print dialog and create a printer DC for you. A minimal OnPreparePrinting override looks like this:

BOOL CMyView::OnPreparePrinting (CPrintInfo* pInfo)
    return DoPreparePrinting (pInfo);

A nonzero return from OnPreparePrinting begins the printing process, and a 0 return cancels the print job before it begins. DoPreparePrinting returns 0 if the user cancels the print job by clicking the Cancel button in the Print dialog, if no printers are installed, or if the framework is unable to create a printer DC.

Key CView Print Overridables

Function Description
OnPreparePrinting Called at the onset of a print job. Override to call DoPreparePrinting and to provide the framework with the page count (if known) and other information about the print job.
OnBeginPrinting Called just before printing begins. Override to allocate fonts and other resources required for printing.
OnPrepareDC Called before each page is printed. Override to position the viewport origin and set a clipping region before OnDraw prints the next page.
OnPrint Called before each page is printed. Override to print headers, footers, and other page elements that aren't drawn by OnDraw or to print without relying on OnDraw.
OnEndPrinting Called when printing is finished. Override to deallocate resources allocated in OnBeginPrinting.

Before proceeding, let me take a moment to explain the two basic approaches to printing from an MFC application. The first is to let OnDraw handle both screen output and printed output. The second is to let OnDraw handle screen output and OnPrint handle printed output. Most experienced MFC developers would agree that the let-OnDraw-do-it-all method is highly overrated. It almost inevitably requires you to add print-specific logic to OnDraw, and you usually end up overriding OnPrint anyway to print page numbers, headers, footers, and other page elements that appear only on the printed page. So while it's true that a view's OnDraw function can write to both the screen and the printer, it's usually more practical to put printer output logic in OnPrint and screen output logic in OnDraw. I'll discuss both approaches in this chapter, but I'll emphasize the latter.

More on the OnPreparePrinting Function

The CPrintInfo object passed to OnPreparePrinting contains information describing the parameters of the print job, including the minimum and maximum page numbers. The minimum and maximum page numbers default to 1 and 0xFFFF, respectively, with 0xFFFF signaling the framework that the maximum page number is unknown. If your application knows how many pages the document contains when OnPreparePrinting is called, it should inform MFC by calling CPrintInfo::SetMaxPage before calling DoPreparePrinting:

BOOL CMyView::OnPreparePrinting (CPrintInfo* pInfo)
    pInfo->SetMaxPage (10);
    return DoPreparePrinting (pInfo);

MFC, in turn, displays the maximum page number—in this case, 10—in the To box of the Print dialog.

SetMinPage and SetMaxPage are two of several CPrintInfo member functions you can call to specify print parameters or to query the framework about print options entered by the user. GetFromPage and GetToPage return the starting and ending page numbers the user entered in the Print dialog. Be sure to call them after DoPreparePrinting, because it's DoPreparePrinting that displays the dialog. CPrintInfo also includes several public data members, including an m_pPD variable that points to the initialized CPrintDialog object through which DoPreparePrinting displays the Print dialog. You can use this pointer to customize the Print dialog before it appears on the screen and to extract information from the dialog by calling CPrintDialog functions or accessing CPrintDialog data members directly. Later in the chapter, you'll see an example demonstrating how and why this is done.

The OnBeginPrinting andOnEndPrinting Functions

Often the maximum page number depends on the size of the printable area of each page output from the printer. Unfortunately, until the user has selected a printer and the framework has created a printer DC, you can only guess what that printable area will be. If you don't set the maximum page number in OnPreparePrinting, you should set it in OnBeginPrinting if possible. OnBeginPrinting receives a pointer to an initialized CPrintInfo structure and a pointer to a CDC object representing the printer DC the framework created when you called DoPreparePrinting. You can determine the dimensions of the printable page area in OnBeginPrinting by calling CDC::GetDeviceCaps twice—once with a HORZRES parameter and once with a VERTRES parameter. The following OnBeginPrinting override uses GetDeviceCaps to determine the height of the printable page area in pixels and uses that information to inform the framework how many pages the document contains:

void CMyView::OnBeginPrinting (CDC* pDC, CPrintInfo* pInfo)
    int nPageHeight = pDC->GetDeviceCaps (VERTRES);
    int nDocLength = GetDocument ()->GetDocLength ();
    int nMaxPage = max (1, (nDocLength + (nPageHeight - 1)) /
    pInfo->SetMaxPage (nMaxPage);

In this example, GetDocLength is a document function that returns the length of the document in pixels. CPrintInfo contains a data member named m_rectDraw that describes the printable page area in logical coordinates, but don't try to use m_rectDraw in OnBeginPrinting because it isn't initialized until after OnBeginPrinting returns.

Calling SetMaxPage in either OnPreparePrinting or OnBeginPrinting lets the framework know how many times it should call OnPrint to print a page. If it's impossible (or simply inconvenient) to determine the document length before printing begins, you can perform print-time pagination by overriding OnPrepareDC and setting CPrintInfo::m_bContinuePrinting to TRUE or FALSE each time OnPrepareDC is called. An m_bContinuePrinting value equal to FALSE terminates the print job. If you don't call SetMaxPage, the framework assumes the document is only one page long. Therefore, you must override OnPrepareDC and set m_bContinuePrinting to print documents that are more than one page long if you don't set the maximum page number with SetMaxPage.

OnBeginPrinting is also the best place to create fonts and other GDI resources used in the printing process. Suppose that OnDraw uses a GDI font to output text to the screen and that the font height is based on the current screen metrics. To print a WYSIWYG version of that font on the printer, you must create a separate font that's scaled to printer metrics rather than to screen metrics. By creating the font in OnBeginPrinting and deleting it in OnEndPrinting, you ensure that the font exists only for the period of time that it is needed and also avoid the overhead of re-creating it each time a page is printed.

OnEndPrinting is the counterpart of OnBeginPrinting. It's a great place to free fonts and other resources allocated in OnBeginPrinting. If there are no resources to free, or if you didn't override OnBeginPrinting to begin with, you probably don't need to override OnEndPrinting, either.

The OnPrepareDC Function

OnPrepareDC is called once for each page of the printed document. One reason to override OnPrepareDC is to perform print-time pagination as described in the previous section. Another reason to override OnPrepareDC is to calculate a new viewport origin from the current page number so that OnDraw can output the current page to the printer. Like OnBeginPrinting, OnPrepareDC receives a pointer to a device context and a pointer to a CPrintInfo object. Unlike OnBeginPrinting, OnPrepareDC is called before screen repaints as well as in preparation for outputting a page to the printer. If the call to OnPrepareDC precedes a screen repaint, the CDC pointer refers to a screen DC and the CPrintInfo pointer is NULL. If OnPrepareDC is called as part of the printing process, the CDC pointer references a printer DC and the CPrintInfo pointer is non-NULL. In the latter case, you can obtain the page number of the page that's about to be printed from the CPrintInfo object's public m_nCurPage data member. You can determine whether OnPrepareDC was called for the screen or the printer by calling CDC::IsPrinting through the CDC pointer passed in the parameter list.

The following implementation of OnPrepareDC moves the viewport origin in the y direction so that the device point (0,0)—the pixel in the upper left corner of the printed page—corresponds to the logical point in the upper left corner of the document's current page. m_nPageHeight is a CMyView data member that holds the printable page height:

void CMyView::OnPrepareDC (CDC* pDC, CPrintInfo* pInfo)
    CView::OnPrepareDC (pDC, pInfo);
    if (pDC->IsPrinting ()) { // If printing...
        int y = (pInfo->m_nCurPage - 1) * m_nPageHeight;
        pDC->SetViewportOrg (0, -y);

Setting the viewport origin this way ensures that an OnDraw function that tries to draw the entire document will actually draw only the part that corresponds to the current page. This example assumes that you want to use the entire printable area of the page. Often it's also necessary to set a clipping region to restrict the part of the document that's printed to something less than the page's full printable area. Rectangular regions are created with CRgn::CreateRectRgn and selected into DCs to serve as clipping regions with CDC::SelectClipRgn.

As a rule, you need to override OnPrepareDC only if you use OnDraw to draw to both the screen and the printed page. If you do all your printing from OnPrint, there's no need to override OnPrepareDC. When you do override it, you should call the base class before doing anything else so that the default implementation will get a chance to do its thing. Calling the base class's OnPrepareDC is especially important when your view class is derived from CScrollView because CScrollView::OnPrepareDC sets the viewport origin for screen DCs to match the current scroll position. When a call to CScrollView::OnPrepareDC returns, the DC's mapping mode is set to the mapping mode specified in the call to SetScrollSizes. If your view class isn't derived from CScrollView, OnPrepareDC is a good place to call SetMapMode to set the device context's mapping mode.

The OnPrint Function

After calling OnPrepareDC for a given page, the framework calls CView::OnPrint. Like many other CView printing functions, OnPrint receives a pointer to the printer DC and a pointer to a CPrintInfo object. The default implementation in Viewcore.cpp verifies the validity of pDC and calls OnDraw:

void CView::OnPrint(CDC* pDC, CPrintInfo*)

     // Override and set printing variables based on page number
     OnDraw(pDC);                    // Call Draw 

What you do when you override OnPrint (and whether you override it at all) depends on how the application does its printing. If OnDraw handles both screen output and printed output, override OnPrint to print page elements that don't appear on the screen. The following OnPrint function calls a local member function named PrintHeader to print a header at the top of the page, another local member function named PrintPageNumber to print a page number at the bottom of the page, and OnDraw to print the body of the page:

void CMyView::OnPrint (CDC* pDC, CPrintInfo* pInfo)
    PrintHeader (pDC);
    PrintPageNumber (pDC, pInfo->m_nCurPage);
    // Set the viewport origin and/or clipping region before
    // calling OnDraw...
    OnDraw (pDC);

Any adjustments made to the printer DC with SetViewportOrg or SelectClipRgn so that OnDraw will draw just the part of the document that corresponds to the current page should now be made in OnPrint rather than OnPrepareDC to prevent headers and page numbers from being affected.

If instead you elect to do all your printing from OnPrint, you override OnPrint and include in it code to output one printed page. To determine which page OnPrint has been called to print, check CPrintInfo::m_nCurPage.

CView::OnFilePrint and Other Command Handlers

Printing usually begins when the user selects the Print command from the File menu, so MFC provides a CView::OnFilePrint function you can connect to the ID_FILE_PRINT menu item through the view's message map. Figure 13-1 shows what happens when OnFilePrint is called and when in the printing process each virtual CView printing function is called. It also shows how the MFC print architecture meshes with the Windows print architecture: if you take away the dark rectangles representing the virtual CView functions that the framework calls, you're left with a pretty good schematic of the Windows printing model. Notice that OnPrepareDC is called twice per page when your code executes under Windows 95 or Windows 98. The first call to OnPrepareDC is made to preserve compatibility with 16-bit versions of MFC, which called OnPrepareDC before StartPage and got away with it because in 16-bit Windows EndPage, not StartPage, resets the device context. The second call to OnPrepareDC is made because in Windows 95 and Windows 98, changes made to the device context in the first call to OnPrepareDC are nullified when StartDoc is called.

MFC also provides predefined command IDs and default command handlers for the File menu's Print Preview and Print Setup commands. The File-Print Preview command (ID=ID_FILE_PRINT_PREVIEW) is handled by CView::OnFilePrintPreview, and File-Print Setup (ID=ID_FILE_PRINT_SETUP) is handled by CWinApp::OnFilePrintSetup. Like OnFilePrint, these command handlers are not prewired into the message maps of the classes to which they belong. To enable these handlers, you must do the message mapping yourself. If you use AppWizard to generate the skeleton of an application that prints, the message mapping is done for you. AppWizard also maps ID_FILE_PRINT_DIRECT to CView::OnFilePrint to enable "direct" printing—printing performed not by the user's selecting Print from the File menu but by the user's selecting Print from a document's context menu or dropping a document icon onto a printer.

Click to view at full size.

Figure 13-1. Overview of the MFC print architecture.

Print Previewing

Once a document/view application is endowed with the ability to print, adding print previewing is as simple as adding a Print Preview command to the File menu (ID=ID_FILE_PRINT_PREVIEW) and adding an entry to the message map to connect this command ID to CView::OnFilePrintPreview. A lot of code backs up OnFilePrintPreview (see the MFC source code file Viewprev.cpp for details), but what happens in OnFilePrintPreview is pretty simple. OnFilePrintPreview takes over the frame window and fills it with a view created from a special CScrollView-derived class named CPreviewView. It also adds a toolbar with buttons for going to the next or the previous page, switching between one-page and two-page views, zooming in and out, and so on. CPreviewView::OnDraw draws a white rectangle representing a printed page (or two rectangles if two-page view is selected), sets some scaling parameters so that the printable area of the white rectangle matches the printable area of a real page, and calls OnPrint to draw in the rectangle. As far as your application is concerned, output is being sent to the printer; the same virtual functions that get called during printing also get called during print preview. But in reality, output goes to the print preview window instead.

Part of the magic that makes print previewing work is the fact that the device context referenced in the pDC parameter passed to CView printing functions is actually two device contexts in one. Every CDC object contains two device context handles: one for an "output DC" (m_hDC) and another for an "attribute DC" (m_hAttribDC). MFC uses the output DC for calls that produce physical output and the attribute DC for calls that request information about the device context—the current text color or current background mode, for example. Most of the time, m_hDC and m_hAttribDC hold the same device context handle. But during print previewing, m_hDC references the screen DC where pages are previewed and m_hAttribDC references the printer DC. The result? If your application uses GetDeviceCaps or other CDC functions to query the GDI about the printer's capabilities or the properties of the printed page, the information it gets back is genuine because it comes from the printer DC. But all physical output goes to the screen DC.

When the user closes the print preview window, the framework calls a virtual CView function named OnEndPrintPreview to notify the application that print preview is about to end. The default implementation of OnEndPrintPreview calls OnEndPrinting, reactivates the original view, and destroys the print preview window. Programmers sometimes override OnEndPrintPreview in order to scroll the view of the document to the last page displayed in print preview mode. (By default, the scroll position in the original view is preserved so that scrolling in print preview mode doesn't affect the original view.) The following OnEndPrintPreview override demonstrates how you can link the scroll position in the original view to the scroll position in the print preview window for a CScrollView:

void CMyView::OnEndPrintPreview (CDC* pDC, CPrintInfo* pInfo,
    POINT point, CPreviewView* pView)
    UINT nPage = pInfo->m_nCurPage;
    POINT pt;
    // Convert nPage into a scroll position in pt.
    ScrollToPosition (pt);
    CScrollView::OnEndPrintPreview (pDC, pInfo, point, pView); 

You'll have to supply the code that converts the current page number into a scroll position yourself. Don't rely on the point parameter passed to OnEndPrintPreview to tell you anything; in current versions of MFC, point always equals (0,0). You should call the base class's OnEndPrintPreview function from the overridden version so that the framework can exit print preview mode and restore the frame window to its original state.

If your printing code needs to discriminate between real printing and printing performed in print preview mode, it can check the m_bPreview data member of the CPrintInfo object referenced in calls to OnBeginPrinting, OnPrint, and other print overridables. m_bPreview is nonzero if the document is being previewed and 0 if it isn't. In addition, CPrintInfo::m_nNumPreviewPages can be inspected to determine whether one or two pages are displayed.

0 0



取 消