Lesson 2: Displaying and Printing Application Data

In Lesson 4 of Chapter 3, you learned how a class derived from the MFC CView class is used to render an image of your application data. You learned how MFC provides a CDC class, which encapsulates a device context and a set of drawing functions that operate upon the device context, to render output on a device. You also learned how the framework passes a CDC-derived device context object, which corresponds to the current output device, to the CView::OnDraw() function. In this way, the OnDraw() function provides a centralized location for your application drawing code. The same code is used whether the output target is an application window, a print preview window, or a page on a printing device.

In this lesson, you will learn more about how to use classes derived from CView and CDC. You will also learn about MFC's drawing tool classes, which you use to send your application's data to the screen or to a printing device.

After this lesson, you will be able to:

  • Describe how to set up a logical coordinate system to make your application output appear correctly on any device that conforms to the Windows Graphical Device Interface (GDI).
  • Describe the Windows coordinate mapping modes and explain how they are used.
  • Describe how to implement scrolling for an MFC application.
  • Describe how to use the MFC drawing tool classes to draw in a device context.
  • Understand the printing and print preview processes, and how you can implement custom printing functions.
Estimated lesson time: 40 minutes

Before you proceed with this lesson, you will need to modify the application drawing function so that it displays the application data rather than "Hello World!"

  • To modify the OnDraw() function
    1. Locate the implementation of the CMyAppView::OnDraw() function. Replace the current implementation with the following code:
    2. void CMyAppView::OnDraw(CDC* pDC) {      CMyAppDoc* pDoc = GetDocument();      ASSERT_VALID(pDoc);      CFont aFont;      aFont.CreateFont(16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,            FF_ROMAN, 0);      CFont * pOldFont = pDC->SelectObject(&aFont);      CSize TextSize = pDC->GetTextExtent(pDoc->m_string);      int nLinePos = 10;      for(int i = 0; i < pDoc->m_nLines; i++)      {           pDC->TextOut(10, nLinePos, pDoc->m_string);           nLinePos += TextSize.cy;      }      pDC->SelectObject(pOldFont); }

    3. Build and run the MyApp application.
    4. On the Edit menu, click Data.
    5. In the Edit Document Data dialog box, type a string 60 characters long into the Line text edit control, and the number 20 into the Number of lines edit control.
    6. Click OK to close the dialog box. The line of text that you entered should appear 20 times, as shown in Figure 5.5.
    7. click to view at full size.

      Figure 5.5 MyApp application test output

    Understanding Coordinate Mapping

    Graphical devices render their output across a two-dimensional coordinate system. Screen output is mapped to a pixel grid; printers work with a two-dimensional array of dots on the printed page. The GDI drawing functions that are used by Windows applications specify their output in a similar manner. GDI drawing is measured and scaled in abstract units of measure known as logical units.

    For example, the following line from our OnDraw() function initializes a font object for outputting text into the device context. The font will be created with a height of 16 logical units.

     aFont.CreateFont(16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FF_ROMAN, 0)); 

    The following code moves to the point in the application drawing space with the coordinates (100,200), and draws a horizontal line 200 logical units long to the point (300,200).

     pDC->MoveTo(100, 200); pDC->LineTo(300, 200);

    Logical units have no intrinsic value. It is up to you to specify the actual value, in terms of units of measure on the device, of a logical unit. To provide a standard interface to the many different types of screens, printers, and other output devices that an application might encounter, the GDI implements a coordinate mapping system. You can specify a mapping mode, which determines how the logical units of the application drawing space are to be mapped to the drawing space of the hardware device (specified in device units). The GDI ensures that the mapping will produce consistent output on whatever hardware device is attached.

    The GDI defines a set of eight mapping modes that specify the ratio between logical coordinates and device coordinates. The default mapping mode is identified by the value MM_TEXT. This maps a logical unit to a single pixel on the device, regardless of the device resolution. This can cause problems with printed output, as illustrated by the following exercise.

  • To view the effects of the default MM_TEXT mapping mode
    1. Run the MyApp application.
    2. On the Data menu, click Edit, and type a string of about 60 characters to be displayed 20 times.
    3. If you have a printer installed, click Print on the File menu to print the application data. If you don't have a printer installed, click Print Preview on the File menu to view the data as it would appear on a printed page.

    Notice how the printer output appears extremely small. This is due to the difference in resolution between the screen and printer. The drawing functions are scaling their output in terms of the resolution of the target device. So a character that is 16 pixels high is about 0.21 inches tall on an 800 x 600 monitor. On a 600 x 600 dots per inch printer, it is only about 0.026 inches tall.

    MM_TEXT is one of six fixed mapping modes, which establish predefined mappings between logical units and device units. Other fixed mapping modes map logical units to units of measure on the device. MM_LOENGLISH for example, specifies that a logical unit will correspond to 0.01 inches on the output device. The fixed mapping modes are shown in Table 5.2.

    Table 5.2 Fixed Mapping Modes

    Mapping modeOne unit maps to
    MM_TEXT1 device pixel
    MM_LOENGLISH0.01 inch
    MM_HIENGLISH0.001 inch
    MM_LOMETRIC0.1 millimeter
    MM_HIMETRIC0.01 millimeter
    MM_TWIPS1/1440 inch

    NOTE
    There is no true physical measurement for displays in Windows because a display driver has no knowledge of the actual physical size of the target monitor. Thus the inches and millimeters in the preceding table are really logical values based on the physical size of an idealized monitor. Printer measurements, on the other hand, are physically accurate.

    In addition to the fixed mapping modes, you can use one of the unconstrained mapping modes MM_ISOTROPIC or MM_ANISOTROPIC. These modes do not set up a predefined mapping and so require you to specify the ratio between logical units and device units. When the MM_ISOTROPIC mode is set, the GDI will continually adjust the mapping to ensure that one logical unit maps to the same physical distance in both x and y directions. The MM_ANISOTROPIC mode allows the x and y coordinates to be adjusted independently.

    To specify the ratio between logical units and device units, you must describe two rectangles that specify the relative dimensions of the logical space (the window) and the device space (the viewport). Window dimensions are specified in logical units using the CDC::SetWindowExt() function. Viewport extents are set using CDC::SetViewportExt() and are specified in device units. Information about the current device dimensions can be obtained by using the CDC::GetDeviceCaps() function.

    The following code sets up a mapping mode similar to MM_LOENGLISH:

     pDC->SetMapMode(MM_ANISOTROPIC); pDC->SetViewPortExt(pDC->GetDeviceCaps(LOGPIXELSX),      pDC->GetDeviceCaps(LOGPIXELSY); pDC->SetWindowExt(100, -100);

    The LOGPIXELSX and LOGPIXELSXY parameters to the GetDeviceCaps() function return the number of pixels per logical inch along the width and height of the display. The code specifies that a 1 x 1-inch square on the device is to correspond to a square in the application drawing space that is 100 x 100 logical units. Or to put it another way, one logical unit will correspond to 0.01 inch on the output device.

    Note that the y parameter of the SetWindowExt() function has a negative value. By default, device coordinates follow the Windows convention that the origin is located in the upper left corner of the window and y values increase downward. By specifying an inverse ratio between the window and the viewport y values, you can allow your drawing functions to describe output using the traditional mathematical coordinate system, with y values that increase upward. Be aware that because the origin of the onscreen display window (the position (0, 0)) is set by default to the top left corner of the window, your drawing will not be visible if you use y coordinates that increase upward. You will be drawing in the space above the top of the visible window. Either invert the values of your y coordinates or use the CDC::SetViewportOrg() function to offset the device window origin to a point that corresponds to the bottom left corner of your logical window.

    The fixed mapping modes follow the mathematical convention that x coordinates increase in value toward the right and that y coordinates increase as they go up. The MM_TEXT mapping mode follows the Windows convention that y coordinate values increase as they go down.

    After you have set up a suitable mapping mode for your view, you simply use the GDI drawing functions to render output using logical coordinates. The conversion between logical coordinates and physical (device) coordinates is handled for you. At times, you will need to convert coordinates yourself using the CDC methods LPtoDP() and DPtoLP() because certain types of information are supplied to you only in device coordinates. For example, the location of a mouse-click is supplied as a parameter to the CView::OnLButtonDown() handler function as a pair of physical coordinates. You would have to convert the coordinates if you needed to determine whether the location of the mouse click corresponded to a significant region of your logical drawing space.

    Scrolling Views

    When drawing your application output in logical space, you are limited only by the size of the coordinate range. Drawing coordinates in a Windows 95 or 98 application must lie within the range _32,768 through 32,767. Using the MM_LOENGLISH mapping mode, this range maps to nearly 3000 square feet of physical drawing space—considerably larger than any output device that you are likely to encounter.

    Windows that are not able to display all of the application output in a single frame should implement scroll bars. Scroll bars should be implemented wherever the user can resize the window, and they should appear at the point where the window frame starts to clip the output in the client area.

    Fortunately, MFC makes it easy to add scrolling to your document/view application. You will recall from Lesson 1 of Chapter 2 that the view class for the MyApp application was derived from the CScrollView class. You can implement scrolling yourself in any view class by using the CWnd scrolling functions and handling the WM_VSCROLL and WM_HSCROLL messages, but CScrollView makes it easy for you to implement scrolling by:

    • Managing window and viewport sizes and mapping modes.
    • Scrolling automatically in response to scroll-bar messages.

    The following exercise introduces the scrolling capabilities offered by CScrollView.

  • To view the default scrolling behavior of the MyApp application
    1. Run the MyApp application.
    2. On the Data menu, click Edit, and then type a string of about 60 characters to be displayed 20 times.
    3. Resize the window so that it resembles Figure 5.6.
    4. Figure 5.6 Testing the MyApp scrolling capabilities

    Notice that although scrolling is invoked automatically, it doesn't behave quite as it should. In Figure 5.6, you can see that the right side of the data lines is out of the view and that a horizontal scroll bar has not appeared. You would expect the scroll bars to appear as soon as the frame edges clip the data displayed in the window. Also notice that when you shrink the window further so that a horizontal scroll bar appears, you cannot scroll across to see an entire data line.

    The reason for this behavior is that it is your responsibility to specify a logical size and a mapping mode for a scrolling view. When an AppWizard application is created with a view derived from CScrollView, the AppWizard adds the following code to the overloaded CView::OnInitialUpdate() method:

     CSize sizeTotal; // TODO: calculate the total size of this view sizeTotal.cx = sizeTotal.cy = 100; SetScrollSizes(MM_TEXT, sizeTotal);

    The CScrollView::SetScrollSizes() function is used to specify the logical size and a mapping mode for the scrolling view. The default OnInitialUpdate() method will set up the view window with dimensions of 100 x 100 logical units. Under the MM_TEXT mapping mode, this corresponds to an onscreen viewport that is 100 x 100 pixels—about a square inch on an 800 x 600 monitor. The SetScrollSizes() function has additional parameters that specify the line size and the page size. The line size is the horizontal and vertical distance to scroll in each direction when the user clicks on a scroll arrow. The page size is the distance to scroll when the user clicks in the scroll bar, but not on a scroll arrow. These last two parameters have default values that are calculated to be in proportion to the total size of the window.

    When the MyApp application window is large enough, you can see all the text. If the window is resized so that the view becomes smaller than the size set in the SetScrollSizes() function, the scroll bars are displayed. The scroll bar scale and positions are also calculated on the basis of the size of the scrolling view. This is why, when you were experimenting with the default scrolling behavior of the MyApp application, it was impossible to scroll to see data that lay outside the 100 x 100 logical window area.

    To set up a simple scrolling view for a document/view application, you need to decide how large a logical drawing space is required to render your document data. Then determine a suitable mapping mode to scale your drawing to an output device, and set the values with a call to SetScrollSizes() in the view class's OnInitialUpdate() function. The following code snippet illustrates how you might set up a view of 8.5 x 11 inches:

     sizeTotal.cx = 850; sizeTotal.cy = 1100; SetScrollSizes(MM_LOENGLISH, sizeTotal);

    This approach is appropriate if you can determine a fixed size for your document data. However, consider an application such as a word processing application, which does not place a constraint on the length of a document. As the user adds lines of text, you will be required to resize the logical window and to recalculate the scale of the scroll bars. In such a case, you will need to call the SetScrollSizes() function frequently as the size of the document data changes.

    In the following exercise, you will use the SetScrollSizes() function to adjust the CMyAppView view so that the scroll bars will appear as the displayed data is obscured by the window frame. The SetScrollSizes() function will be called from within the drawing function so that the scroll sizes will be adjusted according to the size of the string that is displayed. The function will also set the mapping mode to MM_LOENGLISH so that the output will appear consistently on the screen and the printer.

  • To set the scroll sizes for the MyApp application
    1. In ClassView, expand the CMyAppView class icon.
    2. Double click the OnDraw() icon to edit the method.
    3. Beneath the following line,
    4. CSize TextSize = pDC->GetTextExtent(pDoc->m_string);

      add these lines:

      CSize scrollArea =        CSize(TextSize.cx, TextSize.cy * pDoc->m_nLines); // Allow a margin scrollArea += CSize(20, 20); SetScrollSizes(MM_LOENGLISH, scrollArea);

    The scroll sizes are set relative to the size of the data that will be displayed and are specified by the length of the string and the number of lines that are to be displayed.

    When the MM_TEXT mapping mode is used, the y coordinate values increase downward and the other fixed mapping modes follow the mathematical convention, with y values that increase upward. As you have changed the mapping mode from the default MM_TEXT to MM_LOENGLISH, you will need to invert the y coordinates in your display code so that their values decrease as you proceed down the page.

  • To invert the y coordinates in your display code
    1. Locate the following lines of code in the OnDraw() function:
    2.  int nPos = 10; for(int i = 0; i < pDoc->m_nLines; i++) {      pDC->TextOut(10, nPos, pDoc->m_string);      nPos += TextSize.cy; } 

    3. Change the initial value of nPos to _10.
    4. Change the operator that increments nPos in the loop to _ =. The entire function should now look as follows:
    5.  void CMyAppView::OnDraw(CDC* pDC) {      CMyAppDoc* pDoc = GetDocument();      ASSERT_VALID(pDoc);      CFont aFont;      aFont.CreateFont(16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,            FF_ROMAN, 0);      CFont * pOldFont = pDC->SelectObject(&aFont);      CSize TextSize = pDC->GetTextExtent(pDoc->m_string);      CSize scrollArea =             CSize(TextSize.cx, TextSize.cy * pDoc->m_nLines);      // Allow a margin      scrollArea += CSize(20, 20);      SetScrollSizes(MM_LOENGLISH, scrollArea);      int nPos = -10;      for(int i = 0; i < pDoc->m_nLines; i++)      {           pDC->TextOut(10, nPos, pDoc->m_string);           nPos -= TextSize.cy;      }      pDC->SelectObject(pOldFont); } 

    6. Build and run the MyApp application.
    7. On the Data menu, click Edit and make 20 long lines of text appear.
    8. Resize the application window and check that the scroll bars appear as the view area becomes smaller than the data display. Try changing the size of the string and the number of lines that are displayed, and notice how the scroll bars automatically scale to fit the size of the data display.
    9. Print the document, or use Print Preview to check that the printed output is a reasonable size.

    Drawing in a Device Context

    The framework passes to your OnDraw() function a pointer to a device context object, which represents the client area of your application window. The CDC class, the base class for all MFC device context objects, provides a number of functions that enable you to draw lines, shapes, and fill areas; output text; and manipulate bitmap patterns. You have met a few of these functions, such as LineTo(), Rectangle(), and TextOut(), in the examples throughout this chapter.

    The drawing functions work together with the MFC drawing tool classes, which include CPen (for drawing lines), CBrush (for filling areas), CFont, and Cbitmap, among others. A drawing tool of each type is selected into the device context for use with the drawing functions. Thus the CDC::Rectangle() function will draw a rectangle in the device context using the current CPen and fill it using the current CBrush.

    The recommended procedure for using the drawing tool graphic objects is as follows:

    1. Use a two-stage creation process to create a graphic object. First declare the object and then initialize it with the type-specific create function, such as CPen::CreatePen().
    2. Using the CDC::SelectObject() function, select the object into the current device context. The SelectObject() function is overloaded in several ways to correspond to the different types of graphic objects that can be selected.
    3. The SelectObject() function returns a pointer to the graphic object that was originally selected. Create a pointer of the appropriate type to save this pointer value.
    4. When done with the current graphic object, use the saved pointer to select the old graphic object back into the device context to restore its state. You should leave the device context just as you found it.

    The OnDraw() function that you implemented for the MyApp application provides a good example of the procedure. The following lines declare and create a new font and select it into the current device context:

     CFont aFont; aFont.CreateFont(16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, FF_ROMAN, 0); CFont * pOldFont = pDC->SelectObject(&aFont); 

    The aFont font is used in subsequent calls to the CDC::TextOut() function to display text in the device context. When the function has finished with the font, it discards the aFont object and uses the following line of code to restore the object stored in the pOldFont pointer:

    pDC->SelectObject(pOldFont);

    TIP
    Rather than saving and restoring each individual object in the device context, you can use the CDC methods SaveDC() and RestoreDC() to save and restore the entire device context.

    In this courseware, we will only describe how to implement a passive display of application data. A fair amount of work needs to go into developing views that support features such as object selection, cut, copy, and paste, the manipulation of on-screen objects by the user, drawing with the mouse, and so forth. The DRAWCLI sample application that is provided with Visual C++ gives a good example of how to implement many of these features.

    Printing Process

    In Lesson 4 of Chapter 3, you learned that the process of rendering an image of your application data on a printed page is simple because the same OnDraw() function is used for both screen and printer output. The functions CView::OnPrint() and CWnd::OnPaint() both call the OnDraw() function to render output.

    When printing, the framework will perform the following tasks:

    • Display the Print dialog box.
    • Create a device context object for the printer.
    • Call the CDC::StartDoc() function to notify the printer that all subsequent pages should be spooled under the same job until a CDC::EndDoc() call occurs. This ensures that documents longer than one page will not be interspersed with other jobs.
    • Repeatedly call the CDC::StartPage() and CDC::EndPage() functions to inform the printer driver of the beginning and end of each page.
    • Call overridable functions in the view at the appropriate times.

    Several of the functions, including those for implementing pagination for the printed document, allocating GDI resources for printing, and sending escape codes to change the printer mode before printing a given page, can be overridden. You can use these functions to customize the printing process. Table 5.3 lists the printing functions that can be overridden (all members of the CView class).

    Table 5.3 Printing Functions That Can Be Overridden

    NameFunction
    OnPreparePrinting()Allows you to modify a CPRINTIINFO structure to insert values into the Print dialog box—commonly used to set the length of the document. Passes the CPRINTIINFO structure to the CView::DoPreparePrinting() method, which displays the dialog box and creates the printer device context object.
    OnBeginPrinting()Allows you to allocate fonts or other GDI resources used for printed output.
    OnPrepareDC()Allows you to adjust attributes of the device context for a given page. If the length of the document hasn't been specified, a check for the end of the document is performed.
    OnPrint()Allows you to print a given page. By default, this function simply calls OnDraw() to render the output, although you can override OnPrint() to provide printed output that is significantly different to the screen display.
    OnEndPrinting()Allows you to disconnect GDI resources.

    Figure 5.7 illustrates the full printing cycle and shows the order in which these functions are called.

    Figure 5.7 The MFC printing cycle

    The Print dialog box in the MyApp application currently allows you to select a range of pages to print. This feature should be made unavailable for an application that allows you to print only one page of data. In the following exercise, you will add code to the CMyAppView::OnPreparePrinting() function to set the maximum number of document printer pages to 1. This has the effect of making the page range selection feature in the Print dialog box unavailable.

  • To set the maximum number of printer pages
    1. Run the MyApp application. On the File menu, click Print to verify that the page range feature is available.
    2. In ClassView, expand the CMyAppView class icon.
    3. Double-click the OnPreparePrinting() icon to edit the method.
    4. To the body of the function, before the return statement, add the following line:
    5. pInfo->SetMaxPage(1);

    6. Build and run the MyApp application. Click Print on the File menu to verify that the page range feature has been made unavailable.

    Print Preview

    When the user selects the Print Preview command from the File menu, the framework creates a CPreviewDC object. Whenever your application performs an operation that sets a characteristic of the printer device context, the framework also performs a similar operation on the preview device context. For example, if your application selects a font for printing, the framework selects a font for screen display that simulates the printer font. Whenever your application sends output to the printer, the framework sends the output to the screen.

    Print Preview differs from printing in the order that the pages of a document are drawn. During printing, the framework loops until a certain range of pages has been rendered. During Print Preview, one or two pages are displayed and then the application waits. No further pages are displayed until the user clicks Next Page or Previous Page. During Print Preview, the application must also respond to WM_PAINT messages, just as it does during ordinary screen display.

    The OnPreparePrinting() function is called when preview mode is invoked, just as it is when beginning a print job. The CPRINTINFO structure passed to the function contains several members whose values you can set to adjust certain characteristics of the print preview operation. For example, you can set the m_nNumPreviewPages member to specify whether you want to preview the document in one-page or two-page mode.

    Modifying Print Preview

    You can rather easily modify the behavior and appearance of print preview in a number of ways. Some of the modifications you can make include those listed on the next page.

    • Causing the print preview window to display a scroll bar for easy access to any page of the document.
    • Causing print preview to maintain the user's position in the document by beginning its display on the current page.
    • Causing different initialization to be performed for print preview and printing.

    Lesson Summary

    The Windows GDI provides a layer of abstraction between applications and the many different types of output devices upon which an application can display output. The graphical output of applications is measured and scaled in logical units. The GDI implements a coordinate mapping system, which determines how the logical units of the application output are to be mapped to the drawing space of the hardware device. This ensures consistent application output on whatever hardware device is attached.

    The GDI defines a set of eight mapping modes that specify the ratio between logical coordinates and device coordinates. You can choose one of the six fixed mapping modes that establish predefined mappings between logical units and units of measure on the device, or one of the two unconstrained mapping modes that allow you to specify the ratio between logical units and device units.

    Scroll bars should be implemented for a window whenever the window is not able to display all of the application output in a single frame. MFC makes it easy to add scrolling to your document/view application by allowing you to derive your application view class from the MFC class CScrollView. It is your responsibility to specify a size and a mapping mode for a scrolling view so that the framework knows when and how to implement scrolling capabilities.

    MFC provides a number of drawing tool classes, which are used in conjunction with the GDI drawing functions to render application output in a device context. Generally, you create and configure drawing tool objects to suit the needs of your application. You select these objects into the device context using the CDC::SelectObject() function, which returns a pointer to the previously selected object. You should always use this pointer to restore the object at the end of your drawing function to preserve the original state of the device context.

    When printing a document, the framework calls a sequence of functions. All the functions are virtual member functions of the CView class. You can override these functions to customize the printing process. The most common task is to implement pagination for the printed document. Other tasks include the allocation of any GDI resources needed for printing, and sending escape codes to change the printer mode before printing a given page. You can also use some of these functions to perform customizations that are specific to the display of application data in a print preview window.



    Microsoft Press - Desktop Applications with Microsoft Visual C++ 6. 0. MCSD Training Kit
    Desktop Applications with Microsoft Visual C++ 6.0 MCSD Training Kit
    ISBN: 0735607958
    EAN: 2147483647
    Year: 1999
    Pages: 95

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net