A simple printing application

One of the most intimidating aspects of Windows programming is printing. Even printing a single document involves lot of work. Fortunately, because of Windows’ device independence the same GDI functions that we used to draw on the screen can be used to draw on a sheet of paper. While printing a document, there are lots of details that one has to account for. These include paginating the output, print previewing, provision for terminating an unfinished job, etc. Print previewing permits the user to see exactly how the printed output would look like before it is sent to the printer. The application framework provides a lot of supportive code, which makes the process of writing code for printing and print previewing that bit easier.

Let us now understand the printing process step by step. To begin with, CView::OnFilePrint( ) calls the virtual function OnPreparePrinting( ). This function is always overridden. A minimal OnPreparePrinting( ) overridden function looks like this:

BOOL myview::OnPreparePrinting ( CPrintInfo *info )
{

return DoPreparePrinting ( info ) ;

}

The DoPreparePrinting( ) function displays a ‘Print’ dialog and creates a printer DC for us. If a non-zero value is returned from OnPreparePrinting( ) the printing process can begin. However, if a zero is returned the print job is cancelled. It is the DoPreparePrinting( ) function which returns a 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 the printer DC.

If we wish, we can set values in the various elements of the ‘Print’ dialog when the DoPreparePrinting( ) function displays it. For this we must use member functions of the CPrintInfo class. For example, we can specify print parameters like the starting and the ending page numbers of the document to be printed as shown below:

BOOL myview::OnPreparePrinting ( CPrintInfo *info )
{

info -> SetMinPage ( 2 ) ;
info -> SetMaxPage ( 3 ) ;
return DoPreparePrinting ( info ) ;

}

The OnBeginPrinting( ) function is called just before the printing begins. When this function is called, two parameters are passed to it: pointer to an initialized CPrintInfo structure and a pointer to a CDC object representing the printer DC the framework created when we called DoPreparePrinting( ).

OnBeginPrinting( ) is a CView virtual function. If it is overridden it’s done so for two puposes:

  1. To set the maximum page number if we haven’t done it already in OnPreparePrinting( ). This is shown in the follow-ing code snippet:
  2. void myview::OnBeginPrinting ( CDC *p, CPrintInfo *info )
    {

    int pageht = p -> GetDeviceCaps ( VERTRES ) ;
    int doclength = GetDocument( ) -> getdoclength( ) ;
    int max_page = max ( 1, ( doclength + ( pageht - 1 ) ) / pageht ) ;
    info -> SetMaxPage ( max_page ) ;

    }

    Here GetDeviceCaps( ) returns the height of the printable page area in pixels and getdoclength( ) is a document function that returns the length of the document in pixels.

  3. To create fonts to be used in the printing process. These fonts are based on the printer metrics rather than the screen metrics.

When the OnPrepareDC( ) function is called two pointers are passed to it—a pointer to a device context and a pointer to a CPrintInfo object. OnPrepareDC( ) is also a virtual function and is called before each page is printed. This function is overridden for two purposes:

  1. To perform print-time pagination: It is important to inform the framework the maximum number of pages in the document using SetMaxPage( ). With this information the framework can decide how many times it should call OnPrint( ) to print a page. If we have not informed this to the framework either in OnPreparePrinting( ) or in OnBeginPrinting( ) then we must do print-time pagination in OnPrepareDC( ). While doing this pagination we should set CPrintInfo::m_bContinuePrinting to TRUE if there are pages remaining to be printed; and to FALSE if the last page is to be printed and the print job is terminated.
  2. To calculate a new viewport origin from the current page number so that OnDraw( ) can output the current page to the printer. This is shown in the following code snippet:

void myview::OnPreapreDC ( CDC *p, CPrintInfo *info )
{

CScrollView::OnPrepareDC ( p, info ) ;
if ( p -> IsPrinting( ) )
{
int y = ( info -> m_nCurPage - 1 ) * pageht ;
p -> SetViewportOrg ( 0, -y ) ;
}

}

Here the viewport origin is moved in the y direction such 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. pageht is a myview data member holding the printable page height. OnPrepareDC( ) is called before repainting the screen as well as before outputting a page to the printer. To determine whether OnPrepareDC( ) was called for the screen or the printer we have called CDC::IsPrinting( ).

Like OnPrepareDC( ), the CView::OnPrint( ) function also gets called by the framework for each page to be printed. This function too, receives a pointer to the printer DC and a pointer to a CPrintInfo object.

OnPrint( ) is a virtual function. Its default implementation contains a call to the OnDraw( ) function. If it is overridden then what code we write in it depends upon whether we are planning to do the printing through the OnDraw( ) function or not. If we plan to do the printing as well as painting on the screen through OnDraw( ) then within OnPrint( ) we should have provision to print page elements that do not appear on the screen. These include headers and footers to be printed on each page. This is shown in the following code snippet:

void myview::OnPrint ( CDC *p, CPrintInfo *info )
{

displayheader ( p ) ;
OnDraw ( p ) ;
displayfooter ( p ) ;

}

Thus, headers and footers can be printed through local member functions, whereas, the body of the page is printed through the OnDraw( ) function.

On the other hand if we decide to do all our printing through OnPrint( ) and do the painting through OnDraw( ) then we should include the code to print one page also in OnPrint( ).

Once the printing is over, the OnEndPrinting( ) function is called. Here any fonts that were created in OnBeginPrinting( ) are freed. This function too is a virtual function. It is usually overridden only if we have also overridden OnBeginPrinting( ).

Now we know when in the printing process each virtual CView printing function is called. Armed with this knowledge we can write a program that can put all this in action.

In this article we would draw a circle and a rectangle, display some text and then try to print it on the printer. We would also provide support for print previewing and print setup. The ‘File’ menu would contain items like ‘Print’, ‘Print Preview’ and ‘Print Setup’. On selecting the ‘Print’ menu item a ‘Print’ dialog box would popup. In this dialog the user can specify printing options like printer type, starting and ending page numbers and the number of copies.

Selecting ‘Print Preview’ option would put the application in the print preview mode such that the user can see exactly how the printed output would look like before it is sent to the printer. On selecting the ‘Print Setup’ menu item a dialog would popup. Using this dialog we can choose the printer, the paper size and the page orientation—portrait or landscape.

MFC provides predefined command ids and default command handlers for the ‘File’ menu items ‘Print’, ‘Print Preview’ and ‘Print Setup’. These items have ids ID_FILE_PRINT, ID_FILE_PRINT_PREVIEW and ID_FILE_PRINT_SETUP. These are handled by the handlers CView::OnFilePrint( ), CView::OnFilePrintPreview( ) & CWinApp::OnFilePrintSetup( ) respectively. To enable these handlers we have used two message map entries in the myview’s message map and one in myapp’s message map.

We have decided to use myview::OnDraw( ) to do the printing as well as the painting. Hence we have not overridden any of the CView printing functions. It is important that the circle and the rectangle should have same proportions irrespective of whether they are being printed on the printer, painted on the screen or display in the print preview window. To ensure this we have set the mapping mode to MM_LOENGLISH. The pixel per each value for screen and printer are different. Hence had we drawn the figures in the default MM_TEXT mapping mode the figures would have appeared smaller on a 600 dpi printer as compared to those on the screen.

By using a mapping mode like MM_LOENGLISH, the logical units are scaled to physical distance and not to pixel counts. This allows the GDI to do the scaling and ensure that OnDraw( ) produces consistent output on screen as well as on the printer.

No comments: