Drawing and Printing with MFC
Although it is possible to avoid having to deal with your own drawing in an application, there are many situations in which you will want to have your application handle its own rendering for controls or views. In Windows programs, this is done using the GDI functions included in the Windows API. MFC provides a framework that makes it easier to handle drawing in C++, although if you are familiar with the Windows Graphics Device Interface (GDI), the change is fairly cosmetic.
In this chapter, you will learn how to
Paint your own windows
Use MFC to work with GDI objects
Do your own printing
Implement Print Preview
If you need to do your own drawing, you will generally do this in one of two places. The first of these is the OnDraw() function of your view class, which will be called by the framework to display your document to the user. The second is in a handler for the WM_PAINT message, which is generally handled by CWnd::OnPaint(). The WM_PAINT message is sent whenever something has occurred that requires you to redraw a portion of your window, or when you explicitly request it.
The rendering that you will do is based upon a device context (DC). The DC contains information about the device that is to be drawn to and various objects, such as pens and fonts, that are currently selected to draw in that device context.
In MFC, device contexts are encapsulated in the CDC class, which also incorporates most of the common GDI calls. Actually, the CDC class incorporates two device contexts: m_hDC, used for output, and m_hAttribDC, used for attribute queries. In most cases, however, you can do all you need to do with CDC member functions and won't need to worry about working with the data members directly. The Windows API GDI functions take a device context handle, and the CDC member functions use the device context contained in the CDC object.
In addition to the basic CDC class, MFC provides a few other classes derived from CDC that are useful for particular situations:
The CPaintDC class is intended for use in OnPaint() handler functions. CPaintDC will call BeginPaint() on construction and EndPaint() on destruction.
The CClientDC class provides a device context for the client area of a window. This can be used for drawing immediate responses to mouse events.
The CWindowDC class provides a device context for the entire window, including both the client and nonclient areas.
The CMetaFileDC provides a device context for Windows metafiles, which allows you to record a series of drawing commands to be replayed later. The member functions of CMetaFileDC are recorded in a metafile.
In this chapter, you will use a few standard structures. In the C API world, these are the POINT, SIZE, and RECT structures. MFC provides classes that wrap these structures, in addition to providing member functions for working with these objects. The MFC classes may be used interchangeably with the GDI structures in most calls.
NOTE |
For any function that takes a POINT, SIZE, or RECT structure, you can substitute a CPoint, CSize, or CRect object. These classes are actually derived from the corresponding structures, so they have the same data structure. |
The CPoint class is equivalent to the POINT structure, which contains two data items, x and y, to denote a particular point. In addition, the CPoint class provides overridden equality and inequality operators (== and !=) that may be used to compare a CPoint to another point. You can add (or subtract) a CPoint to either a point or a size with the +, +=, -, and -= operators. The Offset() function also allows you to add a value to the members of a CPoint.
MFC provides the CSize class to encapsulate the SIZE structure. This provides the cx and cy members, used to denote a rectangular area's size. The CSize class does not provide an Offset() function, but does support the ==, !=, +, -, +=, and -= operators. Just remember that the left operand must be a CSize, but the right operand may be a SIZE or a CSize.
The CRect class is used to work with RECT structures, which incorporate the top, left, bottom, and right members to define a rectangular area. You can use the TopLeft(), BottomRight(), or CenterPoint() functions to return a CPoint representing the appropriate coordinates.
Many other functions require that the CRect be normalized in order to be used properly. That is, the top and left coordinates must be le 636i83g ss than the bottom and right coordinates. The Normalize() function will swap coordinates accordingly.
CRect provides Width() and Height() functions to return the width and height of the rectangle, as well as the IsRectEmpty() function used to test whether the rectangle has a width and height of 0. In addition, the Size() function returns a CSize denoting the size of the rectangle.
CRect overloads the following operators to work on CRect: =, ==, !=, +, -, +=, and -=. In addition, the & and &= operators can be used to compute the intersection of a CRect and a rectangle, whereas the | and |= operators can be used to find a union.
The PtInRect() function can be used to test whether a CRect contains a given point.
There are also several other CRect functions detailed in the online help.
The coordinates used in drawing functions are dependent on the mapping mode currently used in a device context. Different mapping modes use different values for the logical units used, as well as different orientations for the coordinate system. The different mapping modes are detailed in Table 6.1, which lists the direction of positive x and y coordinates, as well as the value of the logical units.
Table 6.1. Mapping modes
Mapping mode |
+X |
+Y |
Logical unit |
MM_TEXT |
Right |
Down |
1 device pixel |
MM_HIENGLISH |
Right |
Up |
0.001 inch |
MM_LOENGLISH |
Right |
Up |
0.01 inch |
MM_HIMETRIC |
Right |
Up |
0.01 millimeter |
MM_LOMETRIC |
Right |
Up |
0.1 millimeter |
MM_TWIPS |
Right |
Up |
1 twip (1/20 of a point, or 1/1440 of an inch) |
In addition, Windows provides two additional mapping modes, which allow an arbitrary logical unit value. MM_ISOTROPIC will use equal X and Y units, preserving a 1:1 aspect ratio; the MM_ANISOTROPIC mode allows independent X and Y units. These settings are generally used to scale a drawing to fit the current size of a window exactly.
To work with the mapping mode of a CDC object, you can use the GetMapMode() and SetMapMode() functions.
NOTE |
Although both Windows NT and Windows 95 support 32-bit values for coordinate parameters, Win95 works with only 16-bit coordinates internally. |
A Windows device context uses several types of objects to affect the output of your drawing. These include pens, fonts, bitmaps, brushes, and palettes. Before making drawing calls, you associate the object you want to use with the device context. MFC provides classes derived from CGdiObject to work with these objects. To see how this works, let's look at the CPen class, which encapsulates Windows pen objects.
MFC uses the CPen class to help you work with GDI pen objects. Pen objects are associated with a device context for use in drawing lines. The current pen object determines the width, color, and pattern of lines drawn with it.
To use a CPen object, you must first create it. The MFC for GDI objects allows you to do this in one of two ways. First, you can create the object and initialize it with the constructor. This process is known as one-stage construction. For example, you can create a new pen with the following code, which creates a pen for drawing a dashed black line five units wide:
CPen myPen(PS_DOT, 5, RGB(0,0,0));The second technique is known as two-stage construction and entails constructing the object, then calling an initialization function to initialize the object. Two-stage construction allows much greater flexibility in error handling in the event the initialization fails. The code looks like this:
CPen myPen;The arguments for either the constructor or CreatePen() include a style for the lines drawn, the width of the lines, and their color. The styles can include PS_SOLID, PS_DASH, PS_DOT, PS_DASHDOT, and PS_DASHDOTDOT. The color is passed as a COLORREF, which can be generated with the RGB() macro. This will create a COLORREF based on the red, green, and blue components of a color.
There are also versions of the constructor and CreatePen() which allow you to create geometric or cosmetic pens, allowing greater flexibility in pen styles.
Once you have created a CPen object, you select it into the device context before it may be used. In general, you will want to save the current pen, select your new pen, use it, and return the old pen to the device context. This is shown in the following example:
CPen myPen;The SelectObject()call is used similarly to select other objects, such as fonts, brushes, and palettes.
MFC uses the CFont class to encapsulate a Windows font, as well as providing member functions to manipulate that font. To work with fonts in MFC, you first create an instance of CFont. Because the constructor takes no parameters, this is trivial.
Next, you initialize the logical font represented by the CFont object. Windows will convert your logical font to a physical font appropriate for the display device when you select the font into the device context.
If you have allowed the user to choose a font from a CFontDialog, you can pass the pointer returned from CFontDialog::GetCurrentFont() to the CFont::CreateFontIndirect() function. This is by far the simplest way to initialize a CFont object.
If you want to select a font within your application, you can use EnumFontFamilies() to retrieve the fonts available for a given device context. CFont::CreatePointFont() can be used to initialize the CFont based on a typeface name and point size.
You can also use CFont::CreateFont() to initialize the CFont object. This will allow you to specify many parameters of the logical font you wish to create.
To use a font in your drawing, you save the current font selected in the device context, select your new font into the device context, do your text output, and select the old font back into the device context. You can do this with CDC::SelectObject(), as you saw for CPen objects.
The GDI brush object is encapsulated in the MFC CBrush class. Brushes determine the color and pattern that are used to fill in regions, such as in the CDC::Polygon(), CDC::PaintRect(), or CDC::PaintRgn() function. Brushes generally come in four different flavors: solid paints in one solid color, hatched paints a crosshatched pattern, patterned paints from a user-defined bitmap, and null paints nothing.
The null brush is useful in situations where you do not want to paint a region that is normally filled. For example, you use a null brush to draw the outline of a rectangle with CDC::Rectangle(), leaving the background intact. (Actually, the null brush is a Windows stock object, which is covered later in this section.)
If you want to create a brush other than a null brush, you can create the CBrush object with no parameters, then call CreateSolidBrush(), CreateHatchBrush(), or CreatePatternBrush(). These calls allow you to select the color and pattern information used to initialize the brush. There are also constructors provided that take the same parameters, allowing you to construct the brush in one step.
The brush is then used by selecting it into the current device context, remembering to save and restore the old brush, as you saw in the CPen example.
Windows color palette objects are used to map the colors that an application wants to use to the colors actually available on a display device. Even when your display is configured to display 256 colors, these 256 colors are chosen from a much wider assortment. The display device's palette assigns real colors to the 256 slots available in the palette.
Normally, Windows will handle the palette for you, adding entries for the colors normally used by Windows and filling in entries to match the colors specified in your application. Windows will approximate these colors if it needs to. If you are developing an application that deals with images, the exact colors used in the palette may be of great interest to you. In this case, you create your own palette.
Palette objects are created with a constructor taking no parameters. CPalette::CreatePalette() is then called to initialize the palette. This call takes a pointer to a LOGPALETTE structure, which holds an array of PALETTEENTRY structures. Each entry holds information about the red, green, and blue components of each color, as well as some other information on how the entry will be used.
You can get a range of palette entries from the logical palette with GetPaletteEntries(). You can then modify them to suit your needs before sending them back to the palette with SetPaletteEntries().
You can then select the palette into the device context with SelectObject(). Be sure to save the old palette and restore it when you are finished. In addition, you can tell Windows to modify the system palette immediately with AnimatePalette().
Be careful when changing palettes, because the system palette is used by all applications. If you delete a color used by another application, this may drastically change the appearance of applications in the background (particularly wallpaper bitmaps).
Windows uses bitmaps to represent a rectangular image, which can be used to display pictures in a window. These may range from simple pictures, such as custom buttons, to large photographic images. CBitmap objects are created by creating a CBitmap instance and initializing it with the data for the bitmap.
The easiest way to initialize a bitmap is to use LoadBitmap() with a resource ID to read the data from a bitmap resource attached to your application. In addition, you can use LoadOEMBitmap() to load a predefined Windows bitmap for many common display elements. There are also several other initialization functions that you can use to create bitmaps that are appropriate for a specified device context.
Once the bitmap is initialized, you can select it into a device context. However, you generally will not be selecting a bitmap directly into the CDC for your output. Instead, you select the bitmap into a compatible device context so that you can then display it in the actual display context with the BitBlt() function. This whole process looks something like this:
void CMoLessDlg::OnPaint()In this example, you can see that you create a compatible device context to load the bitmap. You then use GetObject() to fill in a temporary BITMAP object, which is used to get the dimensions of the bitmap.
All the real drawing is done with the BitBlt() function. The first four parameters specify the location and size of the bitmap to be drawn. The next three parameters specify the source device context and an offset into the bitmap from which to start rendering.
This example uses SRCCOPY for the final, raster operation parameter, which will simply copy the bitmap to the desired location. However, there are many other possible operations that can, in turn, perform operations to combine the bitmap with the existing display, show the bitmap through the pattern specified in a brush, or invert the bitmap.
If you want to be able to edit a bitmap in your application, you can use the CreateBitmapIndirect() function to initialize the bitmap from a BITMAP structure, which specifies all the information about a bitmap other than the actual bits involved. You can then use SetBitmapBits() and GetBitmapBits() to work with the image data.
Windows uses regions for painting an area with the current brush and for clipping, which allows you to trim the area that is actually rendered. This is useful for making sure that your drawing stays within certain bounds or does not overwrite existing information in the display.
In MFC, you can create a region by creating an instance of CRgn and calling one of its create functions. You can use the CreateRectRgn(), CreateEllipticRgn(), or CreatePolygonRgn() function to initialize a region based on simple shapes, or the CreatePolyPolygonRgn() or CreateFromPath() function to specify a much more complex region.
After you have created a few regions, you can combine them to form new regions with CombineRgn(). This call takes the address of the two regions to combine and a parameter specifying how to combine them. This specifies the values shown in Table 6.2.
Table 6.2. Region combine modes
Mode |
Operation performed |
RGN_COPY |
Creates a copy of the first region |
RGN_AND |
Finds the intersection of the two regions |
RGN_OR |
Finds the union of both regions |
RGN_DIFF |
Finds the areas of the first region that are not included in the second |
RGN_XOR |
Combines both regions, removing overlapping areas |
The region you have created can then be used in calls such as FillRgn() or PaintRgn() to paint a region or SelectClipRgn() to set the clipping region. For more on this, see the section titled 'Clipping,' later in this chapter.
There are many basic objects that you will undoubtedly use over and over if you do much work with your own drawing. To help, Windows provides a collection of stock objects that may be selected into a device context with the SelectStockObject() function. This can save you the trouble of creating your own objects.
The SelectStockObject() function takes only one parameter, which specifies the stock object to be selected. Table 6.3 lists the Windows stock objects available.
Table 6.3. Stock objects
Stock object |
Description |
NULL_PEN |
Null pen |
BLACK_PEN |
Black pen |
WHITE_PEN |
White pen |
NULL_BRUSH |
Null brush |
BLACK_BRUSH |
Solid black brush |
WHITE_BRUSH |
Solid white brush |
GRAY_BRUSH |
Solid gray brush |
DKGRAY_BRUSH |
Solid dark gray brush |
LTGRAY_BRUSH |
Solid light gray brush |
HOLLOW_BRUSH |
Hollow brush (like null brush, will not fill in polygons) |
ANSI_FIXED_FONT |
Fixed ANSI system font |
ANSI_VAR_FONT |
Variable ANSI system font |
OEM_FIXED_FONT |
OEM-dependent fixed font |
SYSTEM_FONT |
System font used for menus, dialog box controls, and other text |
SYSTEM_FIXED_FONT |
Fixed-width system font used prior to Windows 3.0 (the newer SYSTEM_FONT is proportional) |
DEFAULT_PALETTE |
Default color palette containing the 20 static colors in the system palette used by Windows |
Now that you've explored a bit of background, let's see how to use the CDC member functions to do some drawing. There are actually a gazillion different member functions provided in CDC, but you will examine only a few of them here. If you think it would be handy to have a particular function, chances are CDC already supports it—check the online documentation.
One of the simplest elements that you can draw is a line, so let's start there. CDC provides many different functions to draw lines, ranging from simple straight lines to multiple Bézier spline curves.
The line drawing functions all draw lines with the currently selected pen. You will look at changing pens in the next section, so for now let's use whichever pen is currently selected.
Each of the line drawing functions will begin drawing from the current position for the device context, as will several other functions. To set the current position, use the MoveTo() function, which will move the pen without drawing anything. You can retrieve the current position with GetCurrentPosition().
Simple straight lines can be drawn with the LineTo() function, which will draw a line from the current position to the specified point with the selected pen. This will move the current position to the new point. The following example shows how you can draw an X in the current window:
CDC* pDC = GetDC();In this example, you use GetDC() to return the device context for the current CWnd. (This is assuming that the drawing is taking place in a member function of a CWnd-based class.) The GetClientRect() function is used to get the coordinates bounding the client area of the window, where you will be drawing.
You then use MoveTo() and LineTo() to draw an X. You can see that both the MoveTo() and LineTo() functions will accept either a point parameter or separate x and y coordinates.
In addition to drawing straight lines with LineTo(), you can draw arcs with Arc(), ArcTo(), and AngleArc(). Because an arc between two points can have two orientations, the GetArcDirection() and SetArcDirection() functions are provided to let you work with the current direction used to draw arcs.
For more control over the kind of curves you can draw, you can use the PolyBezierTo() function to render one or more cubic Bézier splines. This function takes an array of points and an integer, which specifies the number of splines to be drawn. For each spline, there will be three points in the array of points passed. The first two are control points of the spline drawn from the current position to the third point. The PolyBezier() function is similar, only it does not deal with the current location.
The CDC class also provides functions for drawing simple polygons, such as rectangles and ellipses, by way of the Rectangle() and Ellipse() functions. In addition, you can draw any polygon you like with the Polygon() function, or a set of several polygons with PolyPolygon().
Polygons are automatically filled, using the current brush. To draw just the outline of the polygon, you can select a null brush into the device context.
If you are drawing complex polygons, you may want to look at the SetPolyFillMode() function, which allows you to specify the algorithm used to fill in the polygon.
To output simple text to a device context, you can use the TextOut() function, which will render a specified string at the x and y coordinates specified, using the current font for the device context. You can also use TextOut() to render text at the current position by using SetTextAlign() with the flags set to TA_UPDATECP. This will cause TextOut() to ignore the x and y parameters, placing the text at the current position.
In addition, the CDC class provides many other functions allowing you to choose different options for the location and formatting of text drawn to the device context.
You can assign a clipping region to a device context, which will prevent any drawing outside of the region from being rendered. This is useful to contain your drawing to a region within a window, such as for setting margins or preserving a region of a window that you do not want to overwrite.
You can set the clipping region with SelectClipRgn(), which takes a region parameter. You will explore region objects later in this chapter. For now, suffice it to say that a region can include any combination of rectangles and regions defined by straight lines and splines.
Once you have established the clipping region, you can use the PtVisible() function to determine whether a given point lies within the clipping region. In addition, you can use RectVisible() to determine whether any part of a given rectangle lies within the clipping region. These can be very helpful in optimizing your drawing routines, if you check to see whether something will be rendered before wasting your time drawing something that will only be clipped away.
CDC also provides many other functions for working with clipping regions. See the online documentation for a complete reference.
The subject of drawing your own windows could easily make up an entire book on its own. Although we can't cover everything here, the previous sections give you enough information to get started on basic drawing. In addition, let's look at a few other little tidbits that you may find useful.
In many applications, you may wish to allow the user to manipulate objects directly in your display window, particularly if you will be working with OLE objects. MFC provides the CRectTracker class to help you with this. This class can be used to generate borders around an object in your window that may be several different styles, including borders that include resize handles or a hatched pattern over the entire object.
If you are creating applications for Windows NT, you can use the Windows API ::SetWorldTransform() function to translate between the logical or world coordinate system and the page space for a specified device context. This can be used to scale, rotate, shear, or translate your graphics output.
NOTE |
SetWorldTransform() is not supported on Windows 95. |
To use this function, you specify a transformation matrix that combines the matrices used to perform the desired transformation operations. I won't go into the linear algebra required to work with these, but the online documentation does cover some of this.
In Windows programming, sending output to a printer is the same as sending output to the display. You just work with a different device context for each output device. However, the MFC framework handles printing tasks differently than drawing your views or dialogs.
If you use AppWizard to create an application, simple printing will be handled for you, unless you disable the default Print and Print Preview support option in step 4. The framework will take care of presenting the Print dialog and creating a device context for the printer.
By default, the framework then calls the OnDraw() function of your view with a device context for the printer. However, there are occasions where this is not sufficient, such as when you want to print something other than the displayed view, or if your application needs to be more device-specific in its printing routines, such as dealing with different paper sizes or orientation.
When the user selects the Print command from a menu, the framework handles the ID_FILE_PRINT command with its default handler—the OnFilePrint() member of the view class.
Of course, you could create your own handler for this, but why bother? The framework provides mechanisms to customize printing to meet your needs, while also doing several things for you.
CView::OnFilePrint() will start the printing process and loop through the actual printing of each page. Many of the steps in this process involve virtual functions that may be overridden to meet the specific needs of your application.
To start with, OnFilePrint() will call the OnPreparePrinting() function of your view class. The default implementation of this function will call DoPreparePrinting() to display the Print dialog and create the device context to be used for printing.
One of the most common reasons for overriding OnPrepare() printing is to specify the length of the document to be printed. Your document should provide a function to compute the number of pages on which it will be printed. You can then use this value to modify the CPrintInfo structure before calling CView::DoPreparePrinting().
The CPrintInfo structure is used throughout the printing process to hold information about the print job, as you will see when you get to the functions that will be using this information. For your OnPreparePrinting() override, you will be most interested in the SetMinPage() and SetMaxPage() functions, which you can use to specify the pages included in the document. You may also set the m_bDirect flag to TRUE to prevent the display of the Print dialog box when you call DoPreparePrinting(). If you bypass the normal Print dialog, you should use SetToPage() and SetFromPage() to set up the to and from pages that the Print dialog normally sets.
The CPrintInfo structure also includes the m_bPreview flag that indicates whether the document is being shown in Print Preview or actually sent to a printer. Your code should check this flag if you plan to perform different processing for Print and Print Preview. You will look at Print Preview more closely later.
The following example shows a greatly simplified version of OnPreparePrinting(). Here, you assume that the document has a member specifying the number of lines it contains and that each page will hold 55 lines:
BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo)After OnPreparePrinting() is called, OnFilePrint() will create a device context for the selected printer and will call the OnBeginPrinting() function of your view class.
The OnBeginPrinting() function is provided to allow you to do any further initialization required before the framework begins looping through pages. This function is passed a pointer to the printing device context and a pointer to the CPrintInfo structure. If you will be creating GDI objects that will be used in printing many different pages, this is a good place to do so.
OnFilePrint() then calls CDC::StartDoc() to start a print job on the device context, before it begins looping through individual pages. For each page to be printed, OnFilePrint() will call CView::OnPrepareDC(), CDC::StartPage(), CView::OnPaint(), and CDC::EndPage() before starting the next page with OnPrepareDC().
The OnPrepareDC() member of your view class may be overridden to provide any per page initialization you may need to do with the device context. This is a handy place to set up the mapping mode for the DC or to create any fonts required for this page. You may also want to change the viewport origin here if you plan to use the OnDraw() function to print.
In addition, if you have not specified a fixed length for the document, this function should check for the end of the document. You can signal the end of printing by setting the m_bContinuePrinting member of the CPrintInfo structure to FALSE.
If you need to send any special escape codes to the printer for each page, you may do so here with the CDC::Escape() function.
NOTE |
The same OnPrepareDC() function is called before both the OnDraw() function for screen display and the OnPrint() function for printing. Due to this, you should be certain to call the base class version of OnPrepareDC() before doing anything else. You should also check the return of CDC::IsPrinting() before doing anything specific to printing. |
OnFilePrint() then calls CDC::StartPage() to start printing a new page. This notifies the device driver that data for a new page is on its way. OnFilePrint() then calls the OnPrint() member of your view class.
The OnPrint() member of your view class is passed pointers to the device context and the CPrintInfo structure, and is responsible for doing the real work of drawing a page.
However, you may not need to implement this function at all. The default implementation of OnPrint() will simply defer printing to the OnDraw() member of your view, passing the printer DC instead of the usual display DC. If your printed output is similar to the displayed output, this can save a lot of work. The CDC::IsPrinting() member may be useful in your OnDraw() implementation to decide whether to perform certain processing intended only for real printer output.
OnFilePrint() will then call CDC::EndPage() and start printing the next page with OnPrepareDC() unless the last page specified in the CPrintInfo structure has been printed or the m_bContinuePrinting member of CPrintInfo has been set to FALSE. If the printing loop is finished, OnFilePrint() will clean up by calling CDC::EndDoc() to end the print job before calling the OnEndPrinting() member of the view class.
The OnEndPrinting() member of your view class should be overridden to free any GDI resources that were allocated in the OnBeginPrinting() function. Be certain to call the base class implementation so that MFC will clean up its own resources created in the printing process.
Windows users have come to expect that applications will allow them to preview an application's printed output before it is actually committed to paper. This is generally done with the Print Preview command from the File menu. As it turns out, AppWizard will automatically add an entry to the File menu that sends the ID_FILE_PRINT_PREVIEW command. MFC also provides a default handler for this command—OnPrintPreview().
In general, OnPrintPreview() works just like OnFilePrint(), calling the same OnBeginPrinting(), OnPrepareDC(), and OnPrint() functions and looping through pages. The exception is that OnPrintPreview() will set the m_bPreview flag in the CPrintInfo structure, and the device context that is created will be a CPreviewDC instead of a regular CDC.
Because of the greater interaction with the user, the framework handles Print Preview differently. For starters, OnPrintPreview() will not present the Print dialog. Nor does the whole process run in a continuous loop for each page. Instead, the functions normally called in the page loop are called in response to the user's use of the Next Page and Previous Page buttons.
When the Print Preview window is closed, the framework will call OnEndPrintPreview(). The default implementation of this function calls OnEndPrinting(), which should handle all the cleanup that you need to do. However, if you find that you need to do anything special when you are finished previewing, you can do so by overriding OnEndPrintPreview(); just be sure to call the base class implementation in your override.
In this chapter, you have seen how to use the classes that MFC provides to work with the Windows Graphical Device Interface. You also learned how the drawing process can be used within the MFC framework to perform printer output and Print Preview.
|