Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]

.pdf
Скачиваний:
125
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Storing and Printing Documents

 

Number of widths = 4

 

Page 1

Page 2

Page 3

Page 4

=2

 

Wrox

 

Numberofheights

 

 

 

Press

 

 

 

nches

 

 

 

9i

Page 5

Page 6

Page 7

Page 8

 

 

 

6 inches

Figure 17-6

 

 

 

Getting the Overall Document Size

To figure out how many pages a particular document occupies, you need to know how big the sketch is, and for this you want the rectangle that encloses everything in the document. You can do this easily by adding a function GetDocExtent() to the document class, CSketcherDoc. Add the following declaration to the public interface for CSketcherDoc:

CRect GetDocExtent();

// Get the bounding rectangle for the whole document

The implementation is no great problem. The code for it is:

// Get the rectangle enclosing the entire document

CRect CSketcherDoc::GetDocExtent()

 

{

 

CRect DocExtent(0,0,1,1);

// Initial document extent

CRect ElementBound(0,0,0,0);

// Space for element bounding rectangle

POSITION aPosition = m_ElementList.GetHeadPosition();

while(aPosition)

// Loop through all the elements in the list

{

 

// Get the bounding rectangle for the element ElementBound=(m_ElementList.GetNext(aPosition))->GetBoundRect();

// Make coordinates of document extent the outer limits DocExtent.UnionRect(DocExtent, ElementBound);

}

DocExtent.NormalizeRect(); return DocExtent;

}

889

Chapter 17

You can add this function definition to the SketcherDoc.cpp file, or simply add the code if you used the Add > Add Function capability from the pop-up in Class View. The process loops through every element in the document, using the aPosition variable to step through the list and getting the bounding rectangle for each element. The UnionRect() member of the CRect class calculates the smallest rectangle that contains the two rectangles passed as arguments, and puts that value in the CRect object for which the function is called. Therefore, DocExtent keeps increasing in size until all the elements are contained within it. Note that you initialize DocExtent with (0,0,1,1) because the UnionRect() function doesn’t work properly with rectangles that have zero height or width.

Storing Print Data

The OnPreparePrinting() function in the view class is called by the application framework to enable you to initialize the printing process for your document. The basic initialization that’s required is to provide information about how many pages are in the document for the print dialog that displays. You’ll need to store information about the pages that your document requires so you can use it later in the other view functions involved in the printing process. You’ll originate this in the OnPreparePrinting() member of the view class, too, store it in an object of your own class that you’ll define for this purpose, and store a pointer to the object in the CPrintInfo object that the framework makes available. This approach is primarily to show you how this mechanism works; in most cases, you’ll find it easier just to store the data in your view object, mainly because it makes the notation for referencing the data much simpler.

You’ll need to store the number of pages running the width of the document, m_nWidths, and the number of rows of pages down the length of the document, m_nLengths. You’ll also store the upper-left corner of the rectangle enclosing the document data as a CPoint object, m_DocRefPoint, because you’ll use this when you work out the position of a page to be printed from its page number. You can store the file name for the document in a CString object, m_DocTitle, so that you can add it as a title to each page. The definition of the class to accommodate these is:

#pragma once

class CPrintData

{

public:

UINT

m_nWidths;

// Page count for the

width of the document

UINT

m_nLengths;

// Page count for the

length of the document

CPoint m_DocRefPoint;

//

Top left corner of

the document contents

CString m_DocTitle;

//

The name of the document

};

You can add a new header file with the name PrintData.h to the project by right-clicking the Header Files folder in the Solution Explorer pane and then selecting Add > New Item from the pop-up. You can now enter the class definition in the new file.

You don’t need an implementation file for this class. The default constructor (which is automatically generated) is quite adequate here. Because an object of this class is only going to be used transiently, you don’t need to use CObject as a base or to consider any other complication.

The printing process starts with a call to the view class member OnPreparePrinting(), so check out how you should implement that.

890

Storing and Printing Documents

Preparing to Print

The Application wizard added versions of OnPreparePrinting(), OnBeginPrinting() and

OnEndPrinting() to CSketcherView at the outset. The base code provided for OnPreparePrinting() calls DoPreparePrinting() in the return statement, as you can see:

BOOL CSketcherView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

The DoPreparePrinting() function displays the Print dialog box using information about the number of pages to be printed that’s defined in the CPrintInfo object. Whenever possible, you should calculate the number of pages to be printed and store it in the CPrintInfo object before this call occurs. Of course, in many circumstances you may need information from the device context for the printer before you can do this — when you’re printing a document where the number of pages is going to be affected by the size of font to be used, for example — in which case it won’t be possible to get the page count before you call OnPreparePrinting(). In this case, you can compute the number of pages in the OnBeginPrinting() member, which receives a pointer to the device context as an argument. This function is called by the framework after OnPreparePrinting(), so the information entered in the Print dialog box is available. This means that you can also take account of the paper size selected by the user in the Print dialog box.

Assume that the page size is large enough to accommodate a 6 inch by 9 inch area to draw the document data, so you can calculate the number of pages in OnPreparePrinting(). The code for it is:

BOOL CSketcherView::OnPreparePrinting(CPrintInfo* pInfo)

{

pInfo->m_lpUserData = new CPrintData;

// Create a print data object

CSketcherDoc* pDoc = GetDocument();

// Get a document pointer

// Get the whole document area

 

CRect DocExtent = pDoc->GetDocExtent();

 

// Save the reference point for the whole document ((CPrintData*)(pInfo->m_lpUserData))->m_DocRefPoint =

CPoint(DocExtent.left, DocExtent.bottom);

//Get the name of the document file and save it ((CPrintData*)(pInfo->m_lpUserData))->m_DocTitle = pDoc->GetTitle();

//Calculate how many printed page widths of 600 units are required

//to accommodate the width of the document ((CPrintData*)(pInfo->m_lpUserData))->m_nWidths =

static_cast<UINT>(ceil((static_cast<double>(DocExtent.Width()))/600.0));

//Calculate how many printed page lengths of 900 units are required

//to accommodate the document length

((CPrintData*)(pInfo->m_lpUserData))->m_nLengths = static_cast<UINT>(ceil((static_cast<double>(DocExtent.Height()))/900.0));

// Set the first page number as 1 and

891

Chapter 17

// set the last page number as the total number of pages pInfo->SetMinPage(1);

pInfo->SetMaxPage((static_cast<CPrintData*>(pInfo->m_lpUserData))->m_nWidths * (static_cast<CPrintData*>(pInfo->m_lpUserData))->m_nLengths);

return DoPreparePrinting(pInfo);

}

You first create a CPrintData object on the heap and store its address in the pointer m_lpUserData in the CPrintInfo object passed to the function via the pointer pInfo. After getting a pointer to the document, you get the rectangle enclosing all of the elements in the document by calling the function

GetDocExtent() that you added to the document class earlier in this chapter. You then store the corner of this rectangle in the m_DocRefPoint member of the CPrintData object and put the name of the file that contains the document in m_DocTitle.

Referencing the CPrintData object through the pointer in the CPrintInfo object is rather cumbersome. You get to the pointer with the expression pInfo->m_lpUserData, but because the pointer is of type void, you must add a cast to type CPrintData* to get to the m_DocRefPoint member of the object. The full expression to access the reference point for the document is:

(static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocRefPoint

You have to use this approach for all references to members of the CPrintData object, so any expressions using them are festooned with this notation. If you put the data in the view class, you only need to use the name of the data member. Don’t forget to add an #include directive for PrintData.h to the

SketcherView.cpp file.

The next two lines of code calculate the number of pages across the width of the document, and the number of pages required to cover the length. The number of pages to cover the width is computed by dividing the width of the document by the width of the print area of a page, which is 600 units or 6 inches, and rounding up to the next highest integer using the ceil() library function that is defined in the <cmath> header. An #include for this header file also needs to be added to SketcherView.cpp. For example, ceil(2.1) returns 3.0, ceil(2.9) also returns 3.0, and ceil(-2.1) returns -2.0. A similar calculation to that for the number of pages across the width of a document produces the number to cover the length. The product of these two values is the total number of pages to be printed, and this is the value that you’ll supply for the maximum page number.

Cleaning Up After Printing

Because you created the CPrintData object on the heap, you must ensure that it’s deleted when you’re done with it. You do this by adding code to the OnEndPrinting() function:

void CSketcherView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* pInfo)

{

// Delete our print data object

delete static_cast<CPrintData*>(pInfo->m_lpUserData);

}

892

Storing and Printing Documents

That’s all that’s necessary for this function in the Sketcher program, but in some cases you’ll need to do more. Your one-time final cleanup should be done here. Make sure that you remove the comment delimiters (/* */) from the second parameter name; otherwise, your function won’t compile. The default implementation comments out the parameter names because you may not need to refer to them in your code. Because you use the pInfo parameter, you must uncomment it; otherwise, the compiler reports it as undefined.

You don’t need to add anything to the OnBeginPrinting() function in the Sketcher program, but you’d need to add code to allocate any GDI resources, such as pens, if they were required throughout the printing process. You would then delete these as part of the clean up process in OnEndPrinting().

Preparing the Device Context

At the moment, the Sketcher program calls OnPrepareDC(), which sets up the mapping mode as MM_ANISOTROPIC to take account of the scaling factor. You must make some additional changes so that the device context is properly prepared in the case of printing:

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

{

int Scale = m_Scale;

// Store the scale locally

if(pDC->IsPrinting())

 

Scale = 1;

// If we are printing, set scale to 1

CScrollView::OnPrepareDC(pDC, pInfo);

 

CSketcherDoc* pDoc = GetDocument();

 

pDC->SetMapMode(MM_ANISOTROPIC);

// Set the map mode

CSize DocSize = pDoc->GetDocSize();

// Get the document size

// y extent must be negative because

we want MM_LOENGLISH

DocSize.cy = -DocSize.cy;

// Change sign of y

pDC->SetWindowExt(DocSize);

// Now set the window extent

// Get the number of pixels per inch in x and y int xLogPixels = pDC->GetDeviceCaps(LOGPIXELSX); int yLogPixels = pDC->GetDeviceCaps(LOGPIXELSY);

// Calculate the viewport extent in x and y

long xExtent = static_cast<long>(DocSize.cx)*Scale*xLogPixels/100L;

long yExtent = static_cast<long>(DocSize.cy)*Scale*yLogPixels/100L;

pDC->SetViewportExt(static_cast<int>(xExtent), static_cast<int>(-yExtent)); // Set viewport extent

}

This function is called by the framework for output to the printer as well as to the screen. You should make sure that a scale of 1 is used to set the mapping from logical coordinates to device coordinates when you’re printing. If you left everything as it was, the output would be at the current view scale, but you’d need to take account of the scale when calculating how many pages you needed, and how you set the origin for each page.

You can determine whether you have a printer device context or not by calling the IsPrinting() member of the current CDC object, which returns TRUE if you are printing. All you need to do when you have

893

Chapter 17

a printer device context is to set the scale to 1. Of course, you must change the statements lower down which use the scale value, so that they use the local variable Scale rather than the m_Scale member of the view. The values returned by the calls to GetDeviceCaps() with the arguments LOGPIXELSX and LOGPIXELSY return the number of logical points per inch in the x and y directions for your printer when you’re printing, and the equivalent values for your display when you’re drawing to the screen, so this automatically adapts the viewport extent to suit the device to which you’re sending the output.

Printing the Document

You can write the data to the printer device context in the OnPrint() function. This is called once for each page to be printed. You need to add an override for this function to CSketcherView, using Properties window for the class. Select OnPrint from the list of overrides and then click <Add> OnPrint in the right column.

You can obtain the page number of the current page from the m_nCurPage member of the CPrintInfo object and use this value to work out the coordinates of the position in the document that corresponds to the upper-left corner of the current page. The way to do this is best understood using an example, so imagine that you are printing page seven of an eight-page document, as illustrated in Figure 17-7.

m_nCurPage = 2x4 = 8

900*(((m_nCurPage-1)/m_nWidths

900*((7-1)/4) =

900*(6/4) =

900*1 = 900

xOrg = DocDefPoint.x + 1200

yOrg = DocDefPoint.y - 900

 

m_nWidths: Number of widths = 4

 

Page 1

Page 2

Page 3

Page 4

DocDefPoint

 

 

 

xOrg,yOrg

 

 

 

 

=2

 

 

 

 

 

 

 

ofheights

 

 

 

 

 

 

 

 

 

Page 5

 

Page 6

 

Page 7

Page 8

 

 

 

 

 

Number

 

 

 

 

 

 

 

 

 

 

 

 

 

 

units

 

 

 

 

 

 

900

 

 

 

600 units

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

600*((m_nCurPage-1)%m_nWidths)

 

 

 

 

 

 

600*(6%4) = 600*2 = 1200

 

 

 

 

 

 

Figure 17-7

You can get an index to the horizontal position of the page by decrementing the page number by 1 and taking the remainder after dividing by the number of page widths required for the width of the printed area of the document. Multiplying the result by 600 produces the x coordinate of the upper-left corner of the page, relative to the upper-left corner of the rectangle enclosing the elements in the document. Similarly, you can determine the index to the vertical position of the document by dividing the current page number reduced by 1 by the number of page widths required for the horizontal width of the

894

Storing and Printing Documents

document. By multiplying the remainder by 900, you get the relative y coordinate of the upper-left corner of the page. You can express this in two statements as follows:

int xOrg = (static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocRefPoint.x + 600*((pInfo->m_nCurPage - 1)%

((static_cast <CPrintData*>(pInfo->m_lpUserData))->m_nWidths));

int yOrg = (static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocRefPoint.y - 900*((pInfo->m_nCurPage - 1)/

((static_cast <CPrintData*>(pInfo->m_lpUserData))->m_nWidths));

The statements look complicated, but that’s mostly because of the need to access the information stored in the CPrintData object through the pointer in the CPrintInfo object.

It would be nice to print the file name of the document at the top of each page, but you want to be sure you don’t print the document data over the file name. You also want to center the printed area on the page. You can do this by moving the origin of the coordinate system in the printer device context after you have printed the file name. This is illustrated in Figure 17-8.

Page Origin

m_rectDraw.right

 

File Name

This distance is yOffset given by

om

 

 

- (m_rectDraw.bottom - 600)/2

Draw.bott

900

The Printed Page

rect

600

 

m

 

 

 

 

 

The page origin in the

 

 

document maps to here

This distance is xOffset given by

 

(m_rectDraw.right - 600)/2

 

 

Document Origin

xOrg,yOrg

 

DocRefPoint

 

 

 

The Document

Page 7

 

 

Figure 17-8

 

 

895

Chapter 17

Figure 17-8 illustrates the correspondence between the printed page area in the device context and the page to be printed in the reference frame of the document data. Remember that these are in logical coordinates — the equivalent of MM_LOENGLISH in Sketcher — so y is increasingly negative from top to bottom. The page shows the expressions for the offsets from the page origin for the 600 by 900 area where we are going to print the page. You want to print the information from the document in the dashed area shown on the page, so you need to map the xOrg, yOrg point in the document to the position shown in the printed page, which is displaced from the page origin by the offset values xOffset and yOffset.

By default, the origin in the coordinate system that you use to define elements in the document is mapped to the origin of the device context, but you can change this. The CDC object provides a SetWindowOrg() function for this purpose. This enables you to define a point in the document’s logical coordinate system that you want to correspond to the origin in the device context. It’s important to save the old origin that’s returned from the SetWindowOrg() function as a CPoint object. You must restore the old origin when you’ve finished drawing the current page; otherwise, the m_rectDraw member of the CPrintInfo object is not set up correctly when you come to print the next page.

The point in the document that you want to map to the origin of the page has the coordinates xOrgxOffset, yOrg+yOffset. This may not be easy to visualize, but remember that by setting the window origin, you’re defining the point that maps to the viewport origin. If you think about it, you should see that the xOrg, yOrg point in the document is where you want it on the page.

The complete code for printing a page of the document is:

// Print a page of the document

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

{

// Output the document file name

 

pDC->SetTextAlign(TA_CENTER);

// Center the following text

pDC->TextOut(pInfo->m_rectDraw.right/2, -20,

(static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocTitle);

pDC->SetTextAlign(TA_LEFT);

// Left justify text

// Calculate the origin point for the current page

int xOrg = (static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocRefPoint.x + 600*((pInfo->m_nCurPage - 1)%

((static_cast<CPrintData*>(pInfo->m_lpUserData))->m_nWidths));

int yOrg = (static_cast<CPrintData*>(pInfo->m_lpUserData))->m_DocRefPoint.y - 900*((pInfo->m_nCurPage - 1)/

((static_cast<CPrintData*>(pInfo->m_lpUserData))->m_nWidths));

//Calculate offsets to center drawing area on page as positive values int xOffset = (pInfo->m_rectDraw.right - 600)/2;

int yOffset = -(pInfo->m_rectDraw.bottom + 900)/2;

//Change window origin to correspond to current page & save old origin CPoint OldOrg = pDC->SetWindowOrg(xOrg-xOffset, yOrg+yOffset);

//Define a clip rectangle the size of the printed area

pDC->IntersectClipRect(xOrg,yOrg,xOrg+600,yOrg-900);

OnDraw(pDC);

// Draw the whole document

pDC->SelectClipRgn(NULL);

// Remove the clip rectangle

pDC->SetWindowOrg(OldOrg);

// Restore old window origin

}

896

Storing and Printing Documents

The first step is to output the file name that you squirreled away in the CPrintInfo object. The SetTextAlign() function member of the CDC object allows you to define the alignment of subsequent text output in relation to the reference point you supply for the text string in the TextOut() function. The alignment is determined by the constant passed as an argument to the function. You have three possibilities for specifying the alignment of the text:

Constant

Alignment

 

 

TA_LEFT

The point is at the left of the bounding rectangle for the text, so the text is to

 

the right of the point specified. This is default alignment.

TA_RIGHT

The point is at the right of the bounding rectangle for the text, so the text is to

 

the left of the point specified.

TA_CENTER

The point is at the center of the bounding rectangle for the text.

 

 

You define the x coordinate of the file name on the page as half the page width, and the y coordinate as 20 units, which is 0.2 inches, from the top of the page. After outputting the name of the document file as centered text, you reset the text alignment to the default, TA_LEFT, for the text in the document.

The SetTextAlign() function also allows you to change the position of the text vertically by ORing a second flag with the justification flag. The second flag can be any of the following:

Constant

Alignment

 

 

TA_TOP

Aligns the top of the rectangle bounding the text with the point defining the

 

position of the text. This is the default.

TA_BOTTOM

Aligns the bottom of the rectangle bounding the text with the point defining

 

the position of the text.

TA_BASELINE

Aligns the baseline of the font used for the text with the point defining the

 

position of the text.

 

 

The next action in OnPrint() uses the method that I discussed for mapping an area of the document to the current page. You get the document drawn on the page by calling the OnDraw() function that is used to display the document in the view. This potentially draws the entire document, but you can restrict what appears on the page by defining a clip rectangle. A clip rectangle encloses a rectangular area in the device context within which output appears. Output is suppressed outside of the clip rectangle,. It’s also possible to define irregularly shaped areas for clipping called regions.

The initial default clipping area defined in the print device context is the page boundary. You define a clip rectangle which corresponds to the 600 by 900 area centered in the page. This ensures that you draw only in this area, and the file name is not to be overwritten.

After the current page has been drawn, you call SetClipRgn() with a NULL argument to remove the clip rectangle. If you don’t do this, output of the document title is suppressed on all pages after the first because it lies outside the clip rectangle that would otherwise remain in effect in the print process until the next time IntersectClipRect() gets called.

897

Chapter 17

Your final action is to call SetWindowOrg() again to restore the window origin to its original location, as discussed earlier in this chapter.

Getting a Printout of the Document

To get your first printed Sketcher document, you just need to build the project and execute the program (once you’ve fixed any typos). If you try File > Print Preview, you should get something similar to the window shown in Figure 17-9.

Figure 17-9

You get print preview functionality completely for free. The framework uses the code that you’ve supplied for the normal multipage printing operation to produce page images in the print preview window. What you see in the print preview window should be exactly the same as appears on the printed page.

Summar y

In this chapter, you learned how to get a document stored on disk in a form that allows you to read it back and reconstruct its constituent objects using the serialization process supported by MFC. To implement serialization for classes defining document data, you must:

1.

2.

3.

Derive your class directly or indirectly from CObject.

Specify the DECLARE_SERIAL() macro in your class implementation.

Specify the IMPLEMENT_SERIAL() macro in your class definition.

898