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

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

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

Storing and Printing Documents

IMPLEMENT_SERIAL(CRectangle, CElement, VERSION_NUMBER)

IMPLEMENT_SERIAL(CCircle, CElement, VERSION_NUMBER)

IMPLEMENT_SERIAL(CCurve, CElement, VERSION_NUMBER)

IMPLEMENT_SERIAL(CText, CElement, VERSION_NUMBER)

The Serialize() Functions for the Shape Classes

You can now implement the Serialize() member function for each of the shape classes. Start with the

CElement class:

void CElement::Serialize(CArchive& ar)

 

{

 

CObject::Serialize(ar);

// Call the base class function

if (ar.IsStoring())

 

{

 

ar << m_Color

// Store the color,

<< m_EnclosingRect

// and the enclosing rectangle,

<< m_Pen;

// and the pen width

}

 

else

 

{

 

ar >> m_Color

// Retrieve the color,

>> m_EnclosingRect

// and the enclosing rectangle,

>> m_Pen;

// and the pen width

}

 

}

 

 

 

This function is of the same form as the one supplied for you in the CSketcherDoc class. All of the data members defined in CElement are supported by the overloaded extraction and insertion operators, and so everything is done using those operators. Note that you must call the Serialize() member for the CObject class to ensure that the inherited data members are serialized.

For the CLine class, you can code the function as:

void CLine::Serialize(CArchive& ar)

 

{

 

CElement::Serialize(ar);

// Call the base class function

if (ar.IsStoring())

 

{

 

ar << m_StartPoint

// Store the line start point,

<< m_EndPoint;

// and the end point

}

 

else

 

{

 

ar >> m_StartPoint

// Retrieve the line start point,

>> m_EndPoint;

// and the end point

}

 

}

 

 

 

879

Chapter 17

Again, the data members are all supported by the extraction and insertion operators of the CArchive object ar. You call the Serialize() member of the base class CElement to serialize its data members, and this calls the Serialize() member of CObject. You can see how the serialization process cascades through the class hierarchy.

The Serialize() function member of the CRectangle class is simple:

void CRectangle::Serialize(CArchive& ar)

{

CElement::Serialize(ar);

// Call the base class function

}

This calls the direct base class function because the class has no additional data members.

The CCircle class doesn’t have additional data members beyond those inherited from CElement either, so its Serialize() function also just calls the base class function:

void CCircle::Serialize(CArchive& ar)

{

CElement::Serialize(ar);

// Call the base class function

}

 

For the CCurve class, you have surprisingly little work to do. You can code the Serialize() function as follows:

void CCurve::Serialize(CArchive& ar)

 

{

 

CElement::Serialize(ar);

// Call the base class function

m_PointList.Serialize(ar);

// Serialize the list of points

}

 

 

 

After calling the base class Serialize() function, you just call the Serialize() function for the CList object, m_PointList. Objects of any of the CList, CArray, and CMap classes can be serialized in this way because, once again, these classes are all derived from CObject.

The last class for which you need to add an implementation of Serialize() to Elements.cpp is

CText:

void CText::Serialize(CArchive& ar)

 

{

 

CElement::Serialize(ar);

// Call the base class function

if (ar.IsStoring())

 

{

 

ar << m_StartPoint

// Store the start point

<< m_String;

// and the text string

}

 

else

 

{

 

ar >> m_StartPoint

// Retrieve the start point

>> m_String;

// and the text string

}

 

}

 

 

 

880

Storing and Printing Documents

After calling the base class function, you serialize the two data members using the insertion and extraction operators for ar. The CString class, although not derived from CObject, is still fully supported by CArchive with these overloaded operators.

Exercising Serialization

That’s all you have to do to implement the storing and retrieving of documents in the Sketcher program! The save and restore menu options in the file menu are now fully operational without adding any more code. If you build and run Sketcher after incorporating the changes I’ve discussed in this chapter, you’ll be able to save and restore files and be automatically prompted to save a modified document when you try to close it or exit from the program, as shown in Figure 17-2.

Figure 17-2

The prompting works because of the SetModifiedFlag() calls that you added everywhere you update the document. If you click the Yes button in the screen shown in Figure 17-2, you’ll see the File > Save As dialog box shown in Figure 17-3.

This is the standard dialog box for this menu item under Windows. It’s all fully working, supported by code supplied by the framework. The file name for the document has been generated from that assigned when the document was first opened, and the file extension is automatically defined as .ske. The application now has full support for file operations on documents. Easy, wasn’t it?

881

Chapter 17

Figure 17-3

Moving Text

Now is a good time for me to digress briefly to go back and fix a problem that arose in the last chapter. Remember that whenever you try to move a text element, it leaves a trail behind it until the text is positioned on the document again. This is caused by the reliance on ROP drawing in the MoveElement() member of the view:

void CSketcherView::MoveElement(CClientDC& aDC, CPoint& point)

{

CSize Distance = point - m_CursorPos;

// Get move

distance

m_CursorPos = point;

// Set current

point as 1st for next time

// If there is an element, selected, move it if(m_pSelected)

{

aDC.SetROP2(R2_NOTXORPEN);

m_pSelected->Draw(&aDC,m_pSelected); // Draw the element to erase it m_pSelected->Move(Distance); // Now move the element m_pSelected->Draw(&aDC,m_pSelected); // Draw the moved element

}

}

As previously mentioned, setting the drawing mode of the device context to R2_NOTXORPEN won’t remove the trail left by moving the text. You could get around this by using a method of invalidating the rectangles that are affected by the moving elements so that they redraw themselves. This can cause some annoying flicker when an element is moving fast, however. A better solution is to use the invalidation

882

Storing and Printing Documents

method only for the text elements, and the original ROP method for all the other elements, but how do you know which class the selected element belongs to? This is surprisingly simple: you can use an if statement, as follows:

if (m_pSelected->IsKindOf(RUNTIME_CLASS(CText)))

{

// Code here will only be executed if the selected element is of class CText

}

This uses the RUNTIME_CLASS macro to get a pointer to an object of type CRuntimeClass, then passes this pointer to the IsKindOf() member function of m_pSelected. This returns a non-zero result if m_pSelected is of class CText, and returns zero otherwise. The only proviso is that the class you’re checking for must be declared using DECLARE_DYNCREATE or DECLARE_SERIAL macros, which is why I left this fix until now.

There is another way of determining the class type using a facility that is built in to ANSI/ISO C++. The typeid() operator returns a reference to an object of type type_info that encapsulates a pointer to the name of the runtime type of the object or expression that you place between the parentheses. Because you can compare type_info objects using the == operator (or the != operator), you could test whether m_pSelected is of type CText like this:

if (typeid(m_pSelected) == typid(CText))

{

// Code here will only be executed if the selected element is of class CText

}

If you want to use typeid() operator, you must add an #include directive for the <typinfo> ISO/ANSI C++ header. Of course, there is no requirement for the types to be declared using the MFC macros noted for the previous method, although you must ensure that the /GR compiler option that enables runtime type information is specified.

The final code for MoveElement() using the MFC RUNTIME_CLASS macro is as follows:

void CSketcherView::MoveElement(CClientDC& aDC, CPoint& point)

{

CSize Distance = point - m_CursorPos;

// Get move distance

m_CursorPos = point;

// Set current point as 1st for next time

// If there is an element, selected, move it if(m_pSelected)

{

// If the element is text use this method...

if(m_pSelected->IsKindOf(RUNTIME_CLASS(CText)))

{

CRect OldRect=m_pSelected->GetBoundRect();

// Get old

bound rect

m_pSelected->Move(Distance);

// Move the element

CRect NewRect=m_pSelected->GetBoundRect();

// Get new bound rect

OldRect.UnionRect(&OldRect,&NewRect);

// Combine the bound rects

aDC.LPtoDP(OldRect);

// Convert to client coords

OldRect.NormalizeRect();

// Normalize combined area

InvalidateRect(&OldRect);

// Invalidate combined area

UpdateWindow();

// Redraw immediately

883

Chapter 17

m_pSelected->Draw(&aDC,m_pSelected);

// Draw highlighted

return;

 

}

 

// ...otherwise, use this method

 

aDC.SetROP2(R2_NOTXORPEN);

 

m_pSelected->Draw(&aDC,m_pSelected);

// Draw the element to erase it

m_pSelected->Move(Distance);

// Now move the element

m_pSelected->Draw(&aDC,m_pSelected);

// Draw the moved element

}

 

}

 

You can see that the code for invalidating the rectangles that you must use for moving the text is much less elegant than the ROP code that you use for all the other elements. It works, though, as you’ll see for yourself if you make this modification and then build and run the application. If you would like to try the typeid() mechanism for testing the type, just change the condition in the if statement and add the

#include directive for <typeinfo> to SketcherView.cpp.

Printing a Document

Now take a look at printing the document. You already have a basic printing capability implemented in the Sketcher program, courtesy of the Application wizard and the framework. The File > Print, File > Print Setup, and File > Print Preview menu items all work. Selecting the File > Print Preview menu item displays a window showing the current Sketcher document on a page, as shown in Figure 17-4.

Figure 17-4

Whatever is in the current document is placed on a single sheet of paper at the current view scale. If the document’s extent is beyond the boundary of the paper, the section of the document off the paper won’t be printed. If you select the Print button, this page is sent to your printer.

884

Storing and Printing Documents

As a basic capability which you get for free, it’s quite impressive, but it’s not adequate for most purposes. A typical document in our program may well not fit on a page, so you would either want to scale the document to fit, or perhaps more conveniently, print the whole document over as many pages as necessary. You can add your own print processing code to extend the capability of the facilities provided by the framework, but to implement this you first need to understand how printing has been implemented in MFC.

The Printing Process

Printing a document is controlled by the current view. The process is inevitably a bit messy because printing is inherently a messy business, and it potentially involves you in implementing your own versions of quite a number of inherited functions in your view class.

Figure 17-5 shows the logic of the process and the functions involved.

 

 

 

View Members

 

 

 

 

 

• Calculate page count

 

 

 

 

 

1

 

OnPreparePrinting()

 

 

• Call DoPreparePrinting()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

 

OnBeginPrinting()

• Allocate GDI resources

 

CDC::StartDoc()

 

 

 

 

 

 

 

 

 

3

 

FrameworkThe

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

loop while there are more pages

 

 

 

• Change viewpoint origin

 

 

 

 

 

4

 

OnPrepareDC()

 

 

 

 

 

 

 

• Set DC attributes

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CDC::StartPage()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

 

 

 

 

 

• Print headers/footers

 

 

 

 

 

 

 

 

OnPrint()

 

 

 

 

 

 

6

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

• Print current page

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

CDC::EndPage() 7

CDC::EndDoc() 8

 

9

OnEndPrinting()

• De-Allocate GDI resources

Figure 17-5

885

Chapter 17

Figure 17-5 shows how the sequence of events is controlled by the framework and how printing a document involves calling five inherited members of your view class, which you may need to override. The CDC member functions shown on the left side of the diagram communicate with the printer device driver and are called automatically by the framework.

The typical role of each of the functions in the current view during a print operation is specified in the notes alongside it. The sequence in which they are called is indicated by the numbers on the arrows. In practice, you don’t necessarily need to implement all of these functions, only those that you want to for your particular printing requirements. Typically, you’ll want at least to implement your own versions of OnPreparePrinting(), OnPrepareDC() and OnPrint(). You’ll see an example of how these functions can be implemented in the context of the Sketcher program a little later in this chapter.

The output of data to a printer is done in the same way as outputting data to the display — through a device context. The GDI calls that you use to output text or graphics are device-independent, so they work just as well for a printer as they do for a display. The only difference is the device that the CDC object applies to.

The CDC functions in Figure 17-5 communicate with the device driver for the printer. If the document to be printed requires more than one printed page, the process loops back to call the OnPrepareDC() function for each successive new page, as determined by the EndPage() function.

All the functions in your view class that are involved in the printing process are passed a pointer to an object of type CPrintInfo as an argument. This object provides a link between all the functions that manage the printing process, so take a look at the CPrintInfo class in more detail.

The CPrintInfo Class

A CPrintInfo object has a fundamental role in the printing process because it stores information about the print job being executed and details of its status at any time. It also provides functions for accessing and manipulating this data. This object is the means by which information is passed from one view function to another during printing, and between the framework and your view functions.

An object of the CPrintInfo class is created whenever you select the File > Print or File > Print Preview menu options. After being used by each of the functions in the current view that are involved in the printing process, it’s automatically deleted when the print operation ends.

All the data members of CPrintInfo are public. They are:

Member

Usage

 

 

m_pPD

A pointer to the CPrintDialog object that displays the print

 

dialog box.

m_bDirect

This is set to TRUE by the framework if the print operation is to

 

bypass the print dialog box; otherwise, FALSE.

m_bPreview

A member of type BOOL that has the value TRUE if File > Print

 

Preview was selected; otherwise, FALSE.

 

 

886

 

 

Storing and Printing Documents

 

 

 

 

Member

Usage

 

 

 

 

m_bContinuePrinting

A member of type BOOL. If this is set to TRUE, the framework

 

 

continues the printing loop shown in the diagram. If it’s set to

 

 

FALSE, the printing loop ends. You only need to set this variable

 

 

if you don’t pass a page count for the print operation to the

 

 

CPrintInfo object (using the SetMaxPage() member function). In

 

 

this case, you’ll be responsible for signaling when you’re finished

 

 

by setting this variable to FALSE.

 

m_nCurPage

A value of type UINT that stores the page number of the current

 

 

page. Pages are usually numbered starting from 1.

 

m_nNumPreviewPages

A value of type UINT that specifies the number of pages displayed

 

 

in the print preview window. This can be 1 or 2.

 

m_lpUserData

This is of type LPVOID and stores a pointer to an object that you

 

 

create. This allows you to create an object to store additional

 

 

information about the printing operation and associate it with

 

 

the CPrintInfo object.

 

m_rectDraw

A CRect object that defines the usable area of the page in logical

 

 

coordinates.

 

m_strPageDesc

A CString object containing a format string used by the framework to

 

 

display page numbers during print preview.

 

 

 

A CPrintInfo object has the following public member functions:

Function

Description

 

 

SetMinPage(UINT

The argument specifies the number of the first page of the document.

nMinPage)

There is no return value.

SetMaxPage(UINT

The argument specifies the number of the last page of the document.

nMaxPage)

There is no return value.

GetMinPage() const

Returns the number of the first page of the document as type UINT.

GetMaxPage() const

Returns the number of the last page of the document as type UINT.

GetFromPage() const

Returns the number of the first page of the document to be printed as

 

type UINT. This value is set through the print dialog.

GetToPage() const

Returns the number of the last page of the document to be printed as

 

type UINT. This value is set through the print dialog.

 

 

When you’re printing a document consisting of several pages, you need to figure out how many printed pages the document occupies, and store this information in the CPrintInfo object to make it available to the framework. You can do this in your version of the OnPreparePrinting() member of the current view.

887

Chapter 17

To set the number of the first page in the document, you need to call the function SetMinPage() in the CPrintInfo object, which accepts the page number as an argument of type UINT. There’s no return value. To set the number of the last page in the document, you call the function SetMaxPage(), which also accepts the page number as an argument of type UINT and doesn’t return a value. If you later want to retrieve these values, you can call the GetMinPage() and GetMaxPage() functions for the

CPrintInfo object.

The page numbers that you supply are stored in the CPrintDialog object pointed to by the m_pPD member of CPrintInfo, and displayed in the dialog box that pops up when you select File > Print...

from the menu. The user is then able to specify the numbers of the first and last pages that are printed, which you can retrieve by calling the GetFromPage() and GetToPage() members of the CPrintInfo object. In each case, the values returned are of type UINT. The dialog automatically verifies that the numbers of the first and last pages to be printed are within the range you supplied by specifying the minimum and maximum pages of the document.

You now know what functions you can implement in the view class to manage printing for yourself, with the framework doing most of the work. You also know what information is available through the CPrintInfo object passed to the functions concerned with printing. You’ll get a much clearer understanding of the detailed mechanics of printing if you implement a basic multipage print capability for Sketcher documents.

Implementing Multipage Printing

You use the MM_LOENGLISH mapping mode in the Sketcher program to set things up and then switch to MM_ANISOTROPIC. This means that the shapes and the view extent are measured in terms of hundredths of an inch. Of course, with the unit of size a fixed physical measure, ideally you want to print objects at their actual size.

With the document size specified as 3000 by 3000 units, you can create documents up to 30 inches square, which spreads over quite a few sheets of paper if you fill the whole area. It requires a little more effort to work out the number of pages necessary to print a sketch than with a typical text document because in most instances, you’ll need a two-dimensional array of pages to print a complete sketch document.

To avoid overcomplicating the problem, assume that you’re printing a normal sheet of paper (either A4 size or 81/2 by 11 inches) and in portrait orientation (which means the long edge is vertical). With either paper size, you’ll print the document in a central portion of the paper measuring 6 inches by 9 inches.

With these assumptions, you don’t need to worry about the actual paper size; you just need to chop the document into 600 by 900 unit chunks. For a document larger than one page, you’ll divide up the document as illustrated in the example in Figure 17-6.

As you can see, you’ll be numbering the pages row-wise, so in this case pages 1 to 4 are in the first row and pages 5 to 8 are in the second.

888