📄 apc.htm
字号:
</PRE><P>The first thing you'll notice that's different from OnDraw() is the second parameter,the pointer to a CPrintInfo object pInfo. This is where you'll find the details aboutthe current print, specifically the rectangle coordinates for the printer devicecontext you require. There are lots of useful CPrintInfo member variables. Some ofthese are shown in Table C.1.</P><P><H4>TABLE C.1. CPrintInfo MEMBER VARIABLES SPECIFIC TO PRINT INFORMATION.</H4><P><TABLE BORDER="1"> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT"><I>Variable Name</I></TD> <TD ALIGN="LEFT"><I>Description of Contents</I></TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_nCurPage </TD> <TD ALIGN="LEFT">The current page number for multipage prints </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_nNumPreviewPages </TD> <TD ALIGN="LEFT">Either 1 or 2, depending on the preview pages shown </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_rectDraw </TD> <TD ALIGN="LEFT">The coordinates of the print page rectangle </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_pPD </TD> <TD ALIGN="LEFT">Pointer to a CPrintDialog class if the Print dialog box is used </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_bDirect </TD> <TD ALIGN="LEFT">TRUE if the Print dialog box has been bypassed </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_bPreview </TD> <TD ALIGN="LEFT">TRUE if currently in print preview </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_strPageDesc </TD> <TD ALIGN="LEFT">A format string to help generate the page number </TD> </TR> <TR ALIGN="LEFT" VALIGN="TOP"> <TD ALIGN="LEFT">m_lpUserData </TD> <TD ALIGN="LEFT">A pointer that can be used to hold user data </TD> </TR></TABLE></P><P>Some other member variables in CPrintInfo are covered later in this chapter, butfirst you'll need to find the printing rectangle coordinates rather than the window'srectangle. The m_rectDraw member holds the coordinate rectangle of the current printpage. You can use these coordinates with the printer device context in the OnDraw()function. There is a problem though, in that this structure isn't passed to the OnDraw(),but you can copy the coordinates into a member variable held in your CPrintItViewclass.</P><P>Add the following lines to store the rectangle after the // TODO comment, butbefore the CView::OnPrint() call:</P><P><PRE>// ** copy the print rectangle from the pInfo if (pInfo) m_rcPrintRect = pInfo->m_rectDraw;</PRE><P>This will store the printing rectangle in the m_rcPrintRect member of the CPrintItViewclass. You must therefore declare this member variable, which is easily done by right-clickingthe CPrintItView class in the ClassView pane of the Project Workspace view and choosingthe Add Member Variable<B> </B>option. The Variable Type<B> </B>is a CRect, and thedeclaration is obviously m_rcPrintRect. Access should be private because you don'tneed or want any other classes to know about this internal rectangle.</P><P><H3><A NAME="Heading5"></A>Using the Printer Device Context</H3><P>The device context passed to OnPrint() differs slightly from the display contextin that it may have fewer colors and is probably larger that your display. Otherthan these attributes, you can use it to draw in exactly the same way as the screendevice context. This is how you can use the same OnDraw() to print as well as viewin a window. The base class call CView::OnPrint() implements code that does exactlythis.</P><P>The device context holds a flag that you can interrogate via the IsPrinting()function to determine whether you are drawing to a screen-based device context ora printer-based device context. You might use this difference to change the printedoutput from the screen output, or more subtly to adjust the coordinates used to producethe printed output.</P><P>For the sample program it only remains to use the m_rcPrintRect coordinates whenprinting in the OnDraw() function. The code necessary to use the IsPrinting() functionto determine whether the window's client rectangle or the printer's rectangle shouldbe used is shown in Listing C.2. The output produced is shown by the print previewin Figure C.2.</P><P><H4>LISTING C.2. LST23_2.CPP--ADDING PRINTING RECTANGLE SUPPORT TO THE STANDARDOnDraw() IMPLEMENTATION.</H4><PRE>1: // Declare a client rectangle2: CRect rcClient;3:4: // ** Check the device context for printing mode5: if (pDC->IsPrinting())6: {7: // ** Printing, so use the print rectangle8: rcClient = m_rcPrintRect;9: }10: else11: {12: // ** Not printing, so client rect will do13: GetClientRect(&rcClient);14: }15:16: // Convert to logical units17: pDC->DPtoLP(&rcClient);</PRE><P>Notice in Listing C.2 that an if statement is used in line 5 to call the devicecontext's IsPrinting() function. This function returns TRUE if this is a printerdevice context (or preview) and FALSE for any other device contexts. In the printingcase, you can assign the stored print page rectangle to rcClient, as shown in line8. In the normal screen window case you can just use the standard GetClientRect()to find the window's rectangle, as shown in line 13.</P><P>Because you've used a mapping mode, you must convert both printing and displayrectangle coordinates from device units to logical units. This is done by the DPtoLP()function in line 17. If you change and add lines 4-14 to your existing OnDraw() functionand then build and run the application, you should be able to run the print previewas before, with better results (see Figure C.2).</P><P><A HREF="javascript:popUp('23fig02.gif')"><B>FIGURE C.2.</B></A><B> </B><I>Printpreview using the full print page rectangle coordinates.</I></P><P><I></I><H3><A NAME="Heading6"></A>Maintaining the Aspect Ratio</H3><P>As you can see from Figure C.2, because the paper is much longer and thinner thanthe window, the printed output becomes stretched. The relationship between the widthand the height is the aspect ratio<I>.</I> To stop the image from stretching oneway or another, you must keep the same aspect ratio as the image in the window. Thecode in Listing C.2 doesn't try to maintain aspect ratios, which isn't very satisfactoryin most cases, so you would need to add some code to maintain the aspect ratio ofthe printed output.</P><P>The best tactic to use in this case is to find out whether setting either thewidth or the height to the full width or height of the paper will give maximum coverageand then shorten the other dimension to maintain the aspect ratio.</P><P>To do this, you need some information about the paper dimensions and its own aspectratio. There is a device context function that retrieves these details (and manymore) named GetDeviceCaps(). By passing the ASPECTX or ASPECTY flags to GetDeviceCaps(),you can find the relationship between the width of a pixel and its height. If therelationship is 1:1 the pixel is square; otherwise, it is oblong and might differfrom the screen's own aspect ratio. If it differs, you can decide which axis willgive you the largest image, while maintaining the same aspect ratio as the screen.That way you can avoid a stretched looking image.</P><P>Code that does just that in the OnDraw() function is demonstrated in Listing C.3.</P><BLOCKQUOTE> <P><HR><B>DEVICE ASPECT RATIOS</B><BR> </P> <P>For most printers, you'll probably find that the aspect ratio is 1:1. But if you look closely at thermal printer output like those in fax machines, you can see a very distinctive aspect ratio difference in their pixels.<HR></BLOCKQUOTE><H4>LISTING C.3. LST23_3.CPP--MAINTAINING THE ASPECT RATIO WHILE PRODUCINGTHE LARGEST PRINTED REPRESENTATION.</H4><PRE>1: //** Declare a client rectangle and get the client rect2: CRect rcClient;3: GetClientRect(&rcClient);4:5: // ** Check the device context for printing mode6: if (pDC->IsPrinting())7: {8: // ** Find the Print width : Window width ratio9: double dWidthRatio=(double)m_rcPrintRect.Width()/10: (double)rcClient.Width();11:12: // ** Find the Print height : Window height ratio13: double dHeightRatio=(double)m_rcPrintRect.Height()/14: (double)rcClient.Height();15:16: // ** Calculate the device's aspect ratio17: double dAspect=(double)pDC->GetDeviceCaps(ASPECTX)/18: (double)pDC->GetDeviceCaps(ASPECTY);19:20: // ** Find the new relative height21: int nHeight=(int)(rcClient.Height() *22: dWidthRatio * dAspect );23:24: // ** Find the new relative width25: int nWidth=(int)(rcClient.Width() *26: dHeightRatio * (1.0 / dAspect) );27:28: // ** Set the whole rectangle29: rcClient=m_rcPrintRect;30:31: // ** Determine the best fit across or down the page32: if (nHeight > nWidth)33: {34: // ** Down is best, so adjust the width35: rcClient.BottomRight().x=36: m_rcPrintRect.TopLeft().x + nWidth;37: }38: else39: {40: // ** Across is best, so adjust the height41: rcClient.BottomRight().y=42: m_rcPrintRect.TopLeft().y + nHeight;43: }44: }45:46: // Convert to logical units47: pDC->DPtoLP(&rcClient);</PRE><P>Notice that both the screen window and printed case use the window coordinatesthat are found from the GetClientRect() in line 3. In the onscreen window case, nothingelse is done and the code continues as normal.</P><P>However, a lot now happens when printing, if the IsPrinting() test in line 6 returnsTRUE. First, you must find the ratios of the window width to the paper width andthe window height to the paper height. You can find these ratios as shown in lines9 and 13 by dividing the paper dimensions by the window dimensions.</P><P>The next thing you must calculate is the device's own aspect ratio peculiarities.You can use the GetDeviceCaps() function in line 17 to find the ratio of width toheight in the device itself and store the result in dAspect.</P><P>Using these values, you can now calculate the device's comparative width and heightcoordinates in terms of the opposing window dimension, as shown in lines 21 and 25.This calculation, which includes the device aspect ratio for each dimension, willyield the adjusted height for the full page width or vice versa. Now you must decidewhether you can best fit the full width or height of a page and adjust the otherdimension. The condition on line 32 makes this decision based on the bigger widthor height. This means that if you have a tall, thin window, it is better to use thefull height of the paper and adjust the width; conversely, if you have a short, widewindow, it is better to use the full width and adjust the height. Depending on whatis better, the adjustment is made on line 35 or 42 by setting the bottom-right point'sx- or y-coordinate to the adjusted width or height.</P><P>Notice that all the other dimensions are set to rcClient from the paper in theassignment on line 29, so the adjustment is the only change required. After thissection, the program continues and will use the adjusted rectangle to do its drawing.</P><P>If you build and run the application after adding the lines in Listing C.3 tothe OnDraw() function, you should see that printing or previewing the window willnow maintain the same aspect ratio as the onscreen window. If you stretch the windowto make it higher than it is wide, the printed output will use the full height ofthe page rather than the full width, but still maintain the correct aspect ratios.</P><P><H2><A NAME="Heading7"></A>Pagination and Orientation</H2><P>Printing a single page to represent the view in a window is a common requirement,but largely the printing process is concerned with printing large and complex multipagedocuments from the user's sophisticated data. The framework comes to the rescue againand simplifies this process by providing a common Print Setup dialog box and a pageenumeration system to print and preview the specified range of pages.</P><P><H3><A NAME="Heading8"></A>Setting the Start and End Pages</H3><P>The first considerations for a multipage document are the start and end pages,which also indicate how many pages you are going to print. A framework virtual functionin the view class is called first when printing begins. This function is OnPreparePrinting()and it supplies one parameter, the CPrintInfo object pInfo. This is the first timeyou'll see the CPrintInfo, and this is where you can first change it to customizethe print to your requirements. The OnPreparePrinting() function is supplied automaticallyfrom the AppWizard when you create the SDI, so you don't have to add it yourself.You can see the default implementation by double-clicking the OnPreparePrinting()member of the CPrintItView class in the ClassView pane.</P><P>It should look like this:</P><P><PRE>BOOL CPrintItView::OnPreparePrinting(CPrintInfo* pInfo){ // default preparation return DoPreparePrinting(pInfo);}</PRE><P>By default, the DoPreparePrinting() function is called and passed the pInfo pointerto the CPrintInfo object for the print. DoPreparePrinting() sets up the requireddevice context and calls the standard Print dialog box if you are printing (not previewing).This dialog box is covered in more detail in the next section, but first you canset up a range of pages to print by modifying the CPrintInfo object before the DoPreparePrinting().</P><P>To do this, add the following lines before the // default preparation comment:</P><P><PRE>pInfo->SetMinPage(2); pInfo->SetMaxPage(8);</PRE><P>These two member functions of the CPrintInfo class will modify the CPrintInfoobject pointed at by pInfo to set the starting page at page 2 via SetMinPage() andthe ending page at page 8 via SetMaxPage().</P><P>Now when the document is printed, the OnPrint() function will be called six times.The only difference between each of these calls will be the pInfo->m_nCurPagemember variable that will hold the current page as it iterates between 2 and 8.</P><P>Depending on the kind of application you write, the technique you'll use to determinethe number of pages will vary. If you are selling music compact discs and want toprint a brochure of your product range, you would probably fit the cover pictureand review of each CD on one printed page, so if you sell 120 different CDs, you
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -