| Tenouk C & C++ | MFC Home | MDI Serialization 4 | Printing & Print Preview 2 | Download | Site Index |


 

 

 

 

 

 

Module 13a:

Printing and Print Preview 1

 

 

 

 

 

 

 

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:

 

  1. Printing and Print Preview

  2. Windows Printing

  3. Standard Printer Dialogs

  4. Interactive Print Page Selection

  5. Display Pages vs. Printed Pages

  6. Print Preview

  7. Programming for the Printer

  8. The Printer Device Context and the CView::OnDraw Function

  9. The CView::OnPrint Function

  10. Preparing the Device Context: The CView::OnPrepareDC Function

  11. The Start and End of a Print Job

  12. The MYMFC19 Example: A WYSIWYG Print Program

 

 

 

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.

 

Windows Printing

 

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.

 

Standard Print, Print Preview and Print Setup sub menus.

 

Figure 1: Standard Print, Print Preview and Print Setup sub menus.

 

The standard Print dialog.

 

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.

 

The standard Print Setup dialog.

 

Figure 3: The standard Print Setup dialog.

 

During the printing process, the application displays a standard printer status dialog, as shown in Figure 4.

 

The standard printer status dialog.

 

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.

 

Print Preview

 

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:

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.

 

Function

Common Override Behavior

OnPreparePrinting()

Sets first and last page numbers.

OnBeginPrinting()

Creates GDI objects.

OnPrepareDC() (for each page)

Sets mapping mode and optionally detects end of print job.

OnPrint()

Does print-specific output and then calls OnDraw() (for each page)

OnEndPrinting()

Deletes GDI objects.

 

Table 1

 

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.

 

Header File

Source Code File

Class

PoemDoc.h

PoemDoc.cpp

CPoemDoc

StringView.h

StringView.cpp

CStringView

 

Table 2: New name for the view and document class files.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

AppWizard step 6 of 6, renaming the files and selecting CScrollView as a view base class.

 

Figure 5: AppWizard step 6 of 6, renaming the files and selecting CScrollView as a view base class.

 

Renaming the document files.

 

Figure 6: Renaming the document files and class.

 

MYMFC19 project summary.

 

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.

 

public:

    CStringArray m_stringArray;

 

Adding member variable context menu.

 

Figure 8: Adding member variable context menu.

 

 

Adding a CStringArray data member to the CPoemDoc class.

 

Figure 9: Adding a CStringArray data member to the CPoemDoc class.

 

Visual C++ MFC code segment

 

Listing 1.

 

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:

 

private:

    CRect m_rectPrint;

 

Adding another member variable.

 

Figure 10: Adding another member variable.

 

Adding a CRect data member to the CStringView class.

 

Figure 11: Adding a CRect data member to the CStringView class.

 

Visual C++ MFC code segment

 

Listing 2.

 

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:

 

BOOL CPoemDoc::OnNewDocument()

{

    if (!CDocument::OnNewDocument())

        return FALSE;

 

    m_stringArray.SetSize(10);

    m_stringArray[0] = "The pennycandystore beyond the El";

    m_stringArray[1] = "is where I first";

    m_stringArray[2] = "                  fell in love";

    m_stringArray[3] = "                      with unreality";

    m_stringArray[4] = "Jellybeans glowed in the semi-gloom";

    m_stringArray[5] = "of that september afternoon";

    m_stringArray[6] = "A cat upon the counter moved among";

    m_stringArray[7] = "                     the licorice sticks";

    m_stringArray[8] = "                  and tootsie rolls";

    m_stringArray[9] = "           and Oh Boy Gum";

 

    return TRUE;

}

 

Visual C++ MFC code segment

 

Listing 3.

 

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:

 

void CPoemDoc::DeleteContents()

{

    // called before OnNewDocument() and when document is closed

    m_stringArray.RemoveAll();

}

 

Adding the RemoveAll() function to the document class.

 

Figure 12: Adding the RemoveAll() function to the document class.

 

Visual C++ MFC code segment

 

Listing 4.

 

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)

{

    m_stringArray.Serialize(ar);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Visual C++ MFC code segment

 

Listing 5.

 

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:

 

void CStringView::OnInitialUpdate()

{

    CScrollView::OnInitialUpdate();

    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);

}

 

Visual C++ MFC code segment

 

Listing 6.

 

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;

    CString    str;

    CFont      font;

    TEXTMETRIC tm;

 

    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++)

    {

        str.Format("%02d", i);

        pDC->TextOut(i * 1440, 0, str);

    }

    j = -(m_rectPrint.Height() / 1440);

    for (i = 0; i <= j; i++)

    {

        str.Format("%02d", 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,

                    OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

                    DEFAULT_QUALITY, DEFAULT_PITCH | FF_ROMAN,

                    "Times New Roman");

    CFont* pOldFont = (CFont*) pDC->SelectObject(&font);

    pDC->GetTextMetrics(&tm);

    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]);

    }

    pDC->SelectObject(pOldFont);

    TRACE("LOGPIXELSX = %d, LOGPIXELSY = %d\n", pDC->GetDeviceCaps(LOGPIXELSX),

          pDC->GetDeviceCaps(LOGPIXELSY));

    TRACE("HORZSIZE = %d, VERTSIZE = %d\n", pDC->GetDeviceCaps(HORZSIZE),

          pDC->GetDeviceCaps(VERTSIZE));

}

 

Visual C++ MFC code segment

 

Listing 7.

 

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)

{

    pInfo->SetMaxPage(1);

    return DoPreparePrinting(pInfo);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

-------------------------------------------------------------------------------

Visual C++ MFC code segment

 

Listing 8.

 

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)

{

}

 

Visual C++ MFC code segment

 

Listing 9.

 

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.

 

MYMFC19 program output.

 

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.

 

MYMFC19 program output when activating the Print Preview menu.

 

Figure 14: MYMFC19 program output when activating the Print Preview menu.

 

 

Continue on next module...part 2.

 

The LogScrollView.h and LogScrollView.cpp files download.

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN MFC 9.0 class library online documentation - latest version.

  3. MSDN Library

  4. Windows data type.

  5. Win32 programming Tutorial.

  6. The best of C/C++, MFC, Windows and other related books.

  7. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | MDI Serialization 4 | Printing & Print Preview 2 | Download | Site Index |