Special Edition Using Visual C++ 5 - Chapter 6

## Chapter 6

## Drawing on the Screen

- How device contexts control your displays

  You have to know how to manage device contexts before you can display anything on the screen.

- Respond to window-painting requests from Windows

  When Windows knows that your application's display needs updating, it sends a message to the application.

- Create and use fonts, pens, and brushes

  The basic elements that determine how your display looks are the fonts, pens, and brushes that you've created for the device context.

- How to use scrolling windows

  Often, your application will need to display more information than will fit in a window. Scrolling is the answer to this problem, and the MFC CScrollView class makes it simple.

Most applications need to display some type of data in their windows. One would think that, because Windows is a device-independent operating system, creating window displays would be easier than luring a kitten with a saucer of milk. However, it is exactly Windows' device independence that places a little extra burden on the programmer's shoulders. Because you can never know in advance exactly what type of devices may be connected to a user's system, you can't make many assumptions about display capabilities. Functions that draw to the screen must do so indirectly through something called a device context (DC).

Visual C++'s MFC includes many classes that make dealing with DCs easier. These classes encapsulate graphical objects: not only the DC itself, but also pens, brushes, fonts, and more.

### Understanding Device Contexts

As you know, every Windows application (in fact, every computer application) must manipulate data in some way. Most applications must also display data. Unfortunately, because of Windows' device independence, this task is not as straightforward in Windows as it is in a nongraphical operating system like DOS.

Although device independence forces you, the programmer, to deal with data displays indirectly, it helps you by ensuring that your programs run on all popular devices. In most cases, Windows handles devices for you through the device drivers that the user has installed on the system. These device drivers intercept the data that the application needs to display and then translates the data appropriately for the device on which it will appear, whether that is a screen, a printer, or some other output device.

To understand how all of this device independence works, imagine an art teacher trying to design a course of study appropriate for all types of artists. The teacher creates a course outline that stipulates the subject of a project, the suggested colors to be used, the dimensions of the finished project, and so on. What the teacher doesn't stipulate is the surface on which the project will be painted or the materials needed to paint on that surface. In other words, the teacher stipulates only general characteristics. The details of how these characteristics are applied to the finished project are left up to each specific artist.

For example, an artist using oil paints will choose canvas as his drawing surface and oil paints, in the colors suggested by the instructor, as the paint. On the other hand, an artist using watercolors will select watercolor paper on which to create her work, and she will, of course, use watercolors rather than oils for paint. Finally, the charcoal artist will select the appropriate drawing surface for charcoal and will use a single color.

The instructor in the preceding scenario is much like a Windows programmer. The programmer has no idea who may eventually use the program and what kind of system that user may have. The programmer can recommend the colors in which data should be displayed and the coordinates at which the data should appear, for example, but it is the device driver—the Windows artist—that ultimately decides how the data appears.

A system with a VGA monitor may display data with fewer colors than a system with a Super VGA monitor. Likewise, a system with a monochrome monitor displays the data in only a single color. Monitors with high resolutions can display more data than lower-resolution monitors. The device drivers, much like the artists in the imaginary art school, must take the display requirements and fine-tune them to the device on which the data will actually appear. And it is a data structure called a device context that links the application to the device's driver.

A device context (DC) is little more than a data structure that keeps track of the attributes of a window's drawing surface. These attributes include the currently selected pen, brush, and font that will be used to draw on the screen. Unlike an artist, who can have many brushes and pens with which to work, a DC can use only a single pen, brush, or font at a time. If you want to use a pen that draws wider lines, for example, you need to create the new pen and then replace the DC's old pen with the new one. Similarly, if you want to fill shapes with a red brush, you must create the brush and "select it into the DC," which is how Windows programmers describe replacing a tool in a DC.

A window's client area is a versatile surface that can display anything a Windows program can draw. The client area can display any type of data because everything displayed in a window, whether it be text, spreadsheet data, a bitmap, or any other type of data, is displayed graphically. MFC helps you display data by encapsulating Windows' GDI functions and objects into its DC classes.

### Introducing the Paint1 Application

In this chapter, you will build the Paint1 application, which demonstrates fonts, pens, and brushes. Paint1 will use the document/view paradigm discussed in Chapter 5, "Documents and Views", and the view will handle displaying the data. When run, the application will display text in several different fonts. When the user clicks the application, it displays lines drawn with several different pens. After another click it displays boxes filled with a variety of brushes.

The first step in creating Paint1 is to build an empty shell with AppWizard, as first discussed in Chapter 1, "Building Your First Application.". Choose File, New, and select the Projects tab. As shown in Figure 6.1, fill in the project name as Paint1 and fill in an appropriate directory for the project files. Make sure that MFC AppWizard (exe) is selected. Click OK.

Fig. 6.1

Start an AppWizard project workspace called Paint1.

Move through the AppWizard dialog boxes, change the settings to match those in the list that follows, then click Next to move to the next step:

Step 1: Single document

Step 2: Default settings

Step 3: Default settings

Step 4: Uncheck all check boxes

Step 5: Default settings

Step 6: Default settings

After you click Finish on the last step, the New project information box should resemble Figure 6.2. Click OK to create the project.

Fig. 6.2

The starter application for Paint1 is very simple.

Now that you have a starter application, it’s time to add code to make it demonstrate some of the ways an MFC program can display data on the screen. By the time you get to the end of this chapter, the words display context won't make you scratch your head in perplexity.

> Your starter application has menus, but you are going to ignore them completely. It would be quite a bit of work to remove them; just pretend they aren’t there.

### Building the Paint1 Application

In order to build the Paint1 application, you first need to understand how painting and drawing work in an MFC program. Then you can set up the skeleton code to handle the clicks from the user and the three different kinds of display. Finally, you will fill in the code for each kind of display in turn.

Painting in an MFC Program

In Chapter 4, "Messages and Commands," you learned about message maps and how you can tell MFC which functions to call when it receives messages from Windows. One important message that every Windows program with a window must handle is WM_PAINT. Windows sends the WM_PAINT message to an application's window when the window needs to be redrawn. There are several events that cause Windows to send a WM_PAINT message. The first event occurs when the user simply runs the program. In a properly written Windows application, the application's window gets a WM_PAINT message almost immediately after being run, in order to ensure that the appropriate data is displayed from the very start.

Another time a window might receive the WM_PAINT message is when the window has been resized or has recently been uncovered—either fully or partially—by another window. In either case, part of the window that wasn't visible before is now on the screen, and must be updated.

Finally, a program can indirectly send itself a WM_PAINT message by invalidating its client area. Having this ability ensures that an application can change its window's contents almost any time it wishes. For example, a word processor might invalidate its window after the user pastes some text from the Clipboard.

When you studied message maps, you learned to convert a message name to a message-map macro and function name. You now know, for example, that the message-map macro for a WM_PAINT message is ON_WM_PAINT(). You also know that the matching message-map function should be called OnPaint(). This is another case where MFC has already done most of the work of matching a Windows message with its message-response function. (If all this message-map stuff doesn't sound familiar, you might want to review Chapter 4, "Messages and Comands".)

You might guess that the next step for you is to catch the WM_PAINT message, or to override the OnPaint() function that your view class inherited from CView, but you are not going to do that. The code for CView::OnPaint() is in Listing 6.1. As you can see, WM_PAINT is already caught and handled for you.

Listing 6.1—CView::OnPaint()

    void CView::OnPaint()

    {

         // standard paint routine

         CPaintDC dc(this);

         OnPrepareDC(&dc);

         OnDraw(&dc);

    }

CPaintDC is a special class for managing paint DCs, which are device contexts that are used only when responding to WM_PAINT messages. An object of the CPaintDC class does more than just create a DC; it also calls the BeginPaint() Windows API function in the class's constructor and calls EndPaint() in its destructor. When a program responds to WM_PAINT messages, calls to BeginPaint() and EndPaint() are required. The CPaintDC class handles this requirement without your having to get involved in all the messy details. As you can see, the CPaintDC constructor takes a single argument, which is a pointer to the window for which you're creating the DC. The this pointer points to the current view, so it is passed to the constructor to make a DC for the current view.

OnPrepareDC() is a CView function that prepares a DC for use. You’ll learn more about it in Chapter 7, “Printing and Print Preview.”

Switching the Display

The design for Paint1 states that when you click the application's window, the window's display changes. This seemingly magical feat is actually easy to accomplish. You add a member variable to the view to store what kind of display is being done, and change it when the user clicks the window. In other words, the program routes WM_LBUTTONDOWN messages to the OnLButtonDown() message-response function, which sets the m_display flag as appropriate.

First, add the member variable. You must add it by hand rather than through the shortcut menu because the type includes an enum declaration. Open CPaint1View.h from the FileView, and add these lines after the //Attributes comment:

    protected:

         enum {Fonts, Pens, Brushes} m_Display;

> This is an anonymous or unnamed enum. You can learn more about enum types in the electronic book "C++ by Example" included on this book's CD.

Choose ClassView in the Project Workspace pane, expand the classes, expand CPaint1View, and then double-click the constructor, CPaint1View(). Add this line of code in place of the TODO comment:

         m_Display = Fonts;

This initializes the display selector to the font demonstration. You use the display selector in the OnDraw() function called by CView::OnPaint(). AppWizard has created CPaint1View::OnDraw(), but it doesn’t do anything at the moment. Double-click the function name in ClassView, and add the code shown in Listing 6.2 to the function, removing the TODO comment left by AppWizard.

Listing 6.2—Cpainr1View::OnDraw()

    void CPaint1View::OnDraw(CDC* pDC)

    {

         CPaint1Doc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         switch (m_Display)

         {

              case Fonts:

                   ShowFonts(pDC);

                   break;

              case Pens:

                   ShowPens(pDC);

                   break;

              case Brushes:

                   ShowBrushes(pDC);

                   break;

         }

    }

You will write the three functions ShowFonts(), ShowPens(), and ShowBrushes() in upcoming sections of this chapter. Each of these functions uses the same DC pointer that was passed to OnDraw() by OnPaint(). Add them to the class now, by following these steps:

1. Right-click the CPaint1View class in ClassView and select Add Member Function

2. Enter void for the Function Type

3. Enter ShowFonts(CDC* pDC) for the Function Declaration

4. Change the access to protected. Click OK.

5. Repeat steps 1 to 4 for ShowPens(CDC* pDC) and ShowBrushes(CDC* pDC).

The last step in arranging for the display to switch is to catch left mouse clicks and write code in the message-handler to change m_display.

Right-click CPaint1View in the ClassView and select Add Windows Message Handler from the shortcut menu that appears. Double-click WM_LBUTTONDOWN in the New Windows Message and Event Handlers listbox. ClassWizard adds a function called OnLButtonDown() to the view and adds entries to the message map so that this function will be called whenever the user clicks the left mouse button over this view.

Click Edit Existing to edit the empty OnLButtonDown() you just created, and add the code shown in Listing 6.3.

Listing 6.3— CPaint1View::OnLButtonDown()

    void CPaint1View::OnLButtonDown(UINT nFlags, CPoint point)

    {

         if (m_Display == Fonts)

              m_Display = Pens;

         else if (m_Display == Pens)

              m_Display = Brushes;

         else

              m_Display = Fonts;

         Invalidate();

         CView::OnLButtonDown(nFlags, point);

    }

As you can see, depending on its current value, m_display gets set to the next display type in the series. Of course, just changing the value of m_display doesn't accomplish much. The program still needs to redraw the contents of its window. The call to Invalidate() tells Windows that all of the window needs to be repainted. This causes Windows to generate a WM_PAINT message for the window, which means that eventually OnDraw() will be called, and the view will be redrawn as a font, pen, or brush demonstration.

Using Fonts

Changing the font used in a view is a technique you will want to use in a variety of situations. It’s not as simple as you might think, because you can never be sure that any given font is actually installed on the user’s machine. You set up a structure that holds information about the font you want, and attempt to create it, then work with the font you actually got, which might not be quite the font you asked for.

A Windows font is described in the LOGFONT structure, which is outlined in Table 6.1. The LOGFONT structure uses 14 fields to hold a complete description of the font. Many of the fields can be set to 0 or the default values, depending on the program's needs.

Table 6.1—LOGFONT Fields and Their Descriptions

Field | Description

    lfHeight

Height of font in logical units.

    lfWidth

Width of font in logical units.

    lfEscapement

Angle at which to draw the text.

    lfOrientation

Character tilt in tenths of a degree.

    lfWeight

Font weight:

    lfItalic

A nonzero value indicates italics.

    lfUnderline

A nonzero value indicates an underlined font.

    lfStrikeOut

A nonzero value indicates a strikethrough font.

    lfCharSet

Font character set.

    lfOutPrecision

How to match requested font to actual font.

    lfClipPrecision

How to clip characters that run over clip area.

    lfQuality

Print quality of the font.

    lfPitchAndFamily

Pitch and font family.

    lfFaceName

Typeface name.

Some of the terms in Table 6.1 need a little explanation. The first is logical units. How high is a font that has a height of 8 logical units, for example? The meaning of a logical unit depends on the mapping mode you are using, as shown in Table 6.2. The default mapping mode is MM_TEXT, which means that one logical unit is equal to one pixel. Mapping modes are discussed in more detail in Chapter 7, "Printing and Print Preview."

Table 7.2—Mapping Modes

Mode | Unit
MM_HIENGLISH | 0.001 inch
MM_HIMETRIC | 0.01 millimeter
MM_ISOTROPIC | arbitrary
MM_LOENGLISH | 0.01 inch
MM_LOMETRIC | 0.1 millimeter
MM_TEXT | Device pixel
MM_TWIPS | 1/1440 inch

Escapement refers to writing text along an angled line: orientation refers to writing angled text along a flat line. The font weight refers to the thickness of the letters. A number of constants have been defined for use in this field: they are FW_DONTCARE, FW_THIN, FW_EXTRALIGHT, FW_ULTRALIGHT, FW_LIGHT, FW_NORMAL, FW_REGULAR, FW_MEDIUM, FW_SEMIBOLD, FW_DEMIBOLD, FW_BOLD, FW_EXTRABOLD, FW_ULTRABOLD, FW_BLACK, and FW_HEAVY. Not all fonts are available in all weights. There are four character sets available (ANSI_CHARSET, OEM_CHARSET, SYMBOL_CHARSET, and UNICODE_CHARSET) but for writing English text you will almost always use ANSI_CHARSET. (Unicode is discussed in Chapter 28, "Future Explorations.") The last field in the LOGFONT structure is the face name, such as Courier or Helvetica.

Listing 6.4 shows the code you need to add to the empty ShowFonts() function you created earlier.

Listing 6.4—CPaint1View::ShowFonts()

    void CPaint1View::ShowFonts(CDC * pDC)

    {

        // Initialize a LOGFONT structure for the fonts.

        LOGFONT logFont;

        logFont.lfHeight = 8;

        logFont.lfWidth = 0;

        logFont.lfEscapement = 0;

        logFont.lfOrientation = 0;

        logFont.lfWeight = FW_NORMAL;

        logFont.lfItalic = 0;

        logFont.lfUnderline = 0;

        logFont.lfStrikeOut = 0;

        logFont.lfCharSet = ANSI_CHARSET;

        logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;

        logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;

        logFont.lfQuality = PROOF_QUALITY;

        logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;

        strcpy(logFont.lfFaceName, "Times New Roman");

        // Initialize the position of text in the window.

        UINT position = 0;

        // Create and display eight example fonts.

        for (UINT x=0; x<8; ++x)

        {

             // Set the new font's height.

             logFont.lfHeight = 16 + (x * 8);

             // Create a new font and select it into the DC.

             CFont font;

             font.CreateFontIndirect(&logFont);

             CFont* oldFont = pDC->SelectObject(&font);

             // Print text with the new font.

             position += logFont.lfHeight;

             pDC->TextOut(20, position, "A sample font.");

             // Restore the old font to the DC.

             pDC->SelectObject(oldFont);

        }

    }

ShowFonts() starts by setting up a Times Roman font eight pixels high, with a width that best matches the height, and all other attributes set to normal defaults.

In order to show the many fonts that are displayed in its window, the Paint1 application creates its fonts in a for loop, modifying the value of the LOGFONT structure's lfHeight member each time through the loop, using the loop variable, x, to calculate the new font height, like this:

    logFont.lfHeight = 16 + (x * 8);

Because x starts at 0, the first font created in the loop will be 16 pixels high. Each time through the loop, the new font will be eight pixels higher than the previous one.

After setting the font's height, the program creates a CFont object and calls its CreateFontIndirect() function, which attempts to create a CFont object corresponding to the LOGFONT you created. It will change the LOGFONT to describe the CFont that was actually created, given the fonts that are installed on the user’s machine.

After ShowFonts() calls CreateFontIndirect(), the CFont object has been associated with a Windows font. Now you can select it into the DC. Selecting objects into device contexts is a crucial concept in Windows output programming. You cannot use any graphical object, such as a font, directly; instead you select it into the DC and then use the DC. You always save a pointer to the old object that was in the DC (the pointer is returned from the SelectObject() call) and use it to restore the device context by selecting the old object again when you are finished. The same function, SelectObject() is used to select a variety of bojects into a device context: the font you are using in this section, a pen, a brush, or a number of other drawing objects.

After selecting the new font into the DC, you can use the font to draw text on the screen. The local variable position holds the vertical position in the window at which the next line of text should be printed. This position depends upon the height of the current font. After all, if there's not enough space between the lines, the larger fonts will overlap the smaller ones. When Windows created the new font, it stored the font's height (which is most likely the height that you requested, but might not be) in the LOGFONT structure's lfHeight member. By adding the value stored in lfHeight, the program can determine the next position at which to display the line of text. To make the text appear on the screen, ShowFonts() calls TextOut().

TextOut()'s first two arguments are the X and Y coordinates at which to print the text. The third argument is the text to print. Having printed the text, you restore the old font to the DC in case this is the last time through the loop.

Build the application and run it. It should look something like Figure 6.3. If you click the window it will go blank, because the ShowPens() routine doesn’t draw anything. Click again and it is still blank, this time because the ShowBrushes() routine doesn’t draw anything. Click a third time and you are back to the fonts screen.

Fig. 6.3

The font display shows how you can create different types of text output.

Sizing and Positioning the Window

As you can see in Figure 6.3, Paint1 does not display eight different fonts: only seven can fit in the window. To correct this, you need to set the size of the window a little larger than the Windows default. In an MFC program, you do this in the mainframe class PreCreateWindow() function. This is called for you just before the mainframe window is created. The mainframe window surrounds the entire application and governs the size of the view.

PreCreateWindow() takes one parameter, a reference to a CREATESTRUCT structure. The CREATESTRUCT structure contains essential information about the window that is about to be created, as shown in Listing 6.5.

Listing 6.5—The CREATESTRUCT Structure

    typedef struct tagCREATESTRUCT {

       LPVOID    lpCreateParams;

       HANDLE    hInstance;

       HMENU     hMenu;

       HWND      hwndParent;

       int       cy;

       int       cx;

       int       y;

       int       x;

       LONG      style;

       LPCSTR    lpszName;

       LPCSTR    lpszClass;

       DWORD     dwExStyle;

    } CREATESTRUCT;

If you've programmed Windows without application frameworks like MFC, you'll recognize the information stored in the CREATESTRUCT structure. You used to supply much of this information when calling the Windows API function CreateWindow() to create your application's window. Of special interest to MFC programmers are the cx, cy, x, and y members of this structure. By changing cx and cy, you can set the width and height, respectively, of the window. Similarly, modifying x and y changes the window's position. By overriding PreCreateWindow(), you get a chance to fiddle with the CREATESTRUCT structure before Windows uses it to create the window.

AppWizard created a CMainFrame::PreCreateWindow() function. Expand CMainFrame in ClassView, double-click PreCreateWindow() to edit it, and add the code shown in Listing 6.6. This sets the height and width of the application. It also prevents the user from resizing the application, by using the bitwise and operator, &, to turn off the WS_SIZEBOX style bit. Now the user cannot resize the application.

Listing 6.6—CMainFrame::PreCreateWindow()

    BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

    {

         cs.cx = 440;

         cs.cy = 480;

         cs.style &= ~WS_SIZEBOX;

         return CFrameWnd::PreCreateWindow(cs);

    }

It's important that, after your own code in PreCreateWindow(), you call the base class's PreCreateWindow(). Failure to do this will leave you without a valid window, because MFC never gets a chance to pass the CREATESTRUCT structure on to Windows, and so Windows never creates your window. When overriding class member functions, you usually need to call the base class's version.

Build and run Paint1 to confirm that all eight fonts fit in the application’s window. Now you are ready to demonstrate pens.

Using Pens

You'll be pleased to know that pens are much easier to deal with than fonts, mostly because you don't have to fool around with complicated data structures like LOGFONT. In fact, to create a pen, you need only supply the pen's line style, thickness, and color. The Paint1 application's ShowPens() function displays in its window lines drawn using different pens created within a for loop. The code is shown in Listing 6.7.

Listing 6.7—CPaint1View::ShowPens()

    void CPaint1View::ShowPens(CDC * pDC)

    {

         // Initialize the line position.

         UINT position = 10;

         // Draw sixteen lines in the window.

         for (UINT x=0; x<16; ++x)

         {

              // Create a new pen and select it into the DC.

    CPen pen(PS_SOLID, x*2+1, RGB(0, 0, 255));

              CPen* oldPen = pDC->SelectObject(&pen);

              // Draw a line with the new pen.

              position +=  x * 2 + 10;

              pDC->MoveTo(20, position);

              pDC->LineTo(400, position);

              // Restore the old pen to the DC.

              pDC->SelectObject(oldPen);

         }

    }

Within the loop, ShowPens() first creates a custom pen. The constructor takes three parameters. The first is the line's style, one of the styles listed in Table 6.3. (Only solid lines can be drawn with different thicknesses. Patterned lines always have a thickness of 1.) The second argument is the line thickness, which increases each time through the loop. The third argument is the line's color. The RGB macro takes three values for the red, green, and blue color components and converts them into a valid Windows color reference. The values for the red, green, and blue color components can be anything from 0 to 255—the higher the value, the brighter that color component. This code creates a bright blue pen. If all the color values were 0, the pen would be black; if the color values were all 255, the pen would be white.

Table 6.3—Pen Styles

Style | Description

    PS_DASH

Specifies a pen that draws dashed lines

    PS_DASHDOT

Specifies a pen that draws dash-dot patterned lines

    PS_DASHDOTDOT

Specifies a pen that draws dash-dot-dot patterned lines

    PS_DOT

Specifies a pen that draws dotted lines

    PS_INSIDEFRAME

Specifies a pen that's used with shapes, where the line's thickness must not extend outside of the shape's frame

    PS_NULL

Specifies a pen that draws invisible lines

    PS_SOLID

Specifies a pen that draws solid lines

> If you want to control the style of a line's end points or want to create your own custom patterns for pens, you can use the alternate CPen constructor, which requires a few more arguments than the CPen constructor described in this section. To learn how to use this alternate constructor, look up CPen in your Visual C++ online documentation.

After creating the new pen, ShowPens() selects it into the DC, saving the pointer to the old pen. The MoveTo() function moves the pen to an X,Y co-ordinate without drawing as it moves:; the LineTo() function moves the pen while drawing. The style, thickness, and color of the pen are used. Finally, you select the old pen into the DC.

> There are a number of line drawing functions other than LineTo(), including Arc(), ArcTo(), AngleArc(), PolyDraw() and more.

Build and run Paint1 again. When the font display appears, click the window. You should see a pen display like Figure 6.4.

Fig. 6.4

The Pen display shows the effect of setting line thickness.

Using Brushes

A pen draws a line, of a specified thickness, on the screen. A brush fills a shape on the screen. You can create both solid and patterned brushes, and even create brushes from bitmaps that contain your own custom fill patterns. Paint1 will display both patterned and solid rectangles in the ShowBrushes() function, shown in Listing 6.8.

Listing 6.8—CPaint1View::ShowBrushes()

    void CPaint1View::ShowBrushes(CDC * pDC)

         // Initialize the rectangle position.

         UINT position = 0;

         // Select pen to use for rectangle borders

         CPen pen(PS_SOLID, 5, RGB(255, 0, 0));

         CPen* oldPen = pDC->SelectObject(&pen);

         // Draw seven rectangles.

         for (UINT x=0; x<7; ++x)

         {

              CBrush* brush;

              // Create a solid or hatched brush.

              if (x == 6)

                   brush = new CBrush(RGB(0,255,0));

              else

                  brush = new CBrush(x, RGB(0,160,0));

              // Select the new brush into the DC.

              CBrush* oldBrush = pDC->SelectObject(brush);

              // Draw the rectangle.

              position += 50;

              pDC->Rectangle(20, position, 400, position + 40);

              // Restore the DC and delete the brush.

              pDC->SelectObject(oldBrush);

              delete brush;

         }

         // Restore the old pen to the DC.

         pDC->SelectObject(oldPen);

    }

The rectangles painted with the various brushes in this routine will all be drawn with a border. To arrange this, just create a pen (this one is solid, 5 pixels thick, and bright red) and select it into the DC. It will be used to border the rectangles without any further work on your part. Like ShowFonts() and ShowPens(), this routine creates its graphical objects within a for loop. Unlike those two functions, ShowBrushes() creates a graphical object (in this routine, a brush) with a call to new. This allows you to call either the one-argument constructor, which creates a solid brush, or the two argument constructor, which creates a hatched brush.

In Listing 6.8, the first argument to the two argument constructor is just the loop variable, x. Usually you do not want to show all the hatch patterns, but want to select a specific one. Use one of these constants for the hatch style:

- HS_HORIZONTAL Horizontal
- HS_VERTICAL Vertical
- HS_CROSS Horizontal and vertical
- HS_FDIAGONAL Forwards diagonal
- HS_BDIAGONAL Backwards diagonal
- HS_DIAGCROSS Diagonal in both directions

In a pattern that should be familiar by now, ShowBrushes() selects the brush into the DC, determines the position at which to work, uses the brush by calling Rectangle(), and then restores the old brush. When the loop is complete, the old pen is restored as well.

Rectangle() is just one of the shape-drawing functions that you can call. Rectangle() takes as arguments the coordinates of the rectangle's upper-left and lower-right corners. Some others of interest are Chord(), DrawFocusRect(), Ellipse(), Pie(), Polygon(), PolyPolygon(), Polyline(), and RoundRect(), which draws a rectangle with rounded corners.

Once again, build and run Paint1. Click twice, and you should see the demonstration of brushes, as shown in Figure 6.5.

Fig. 6.5

The Brushes display shows a number of different patterns inside thick-bordered rectangles.

> Remember the call to Invalidate() in CPaint1View::OnLButtonDown()? Invalidate() actually takes an argument, with a default value of TRUE. This Boolean argument tells Windows whether to erase the window's background. If you use FALSE for this argument, the background is not erased. In Figure 6.6, you can see what happens to the Paint1 application if Invalidate() gets called with an argument of FALSE.

Fig. 6.6

Without erasing the background, the Paint1 application's windows get a bit messy.

### Scrolling Windows

Those famous screen rectangles called windows were developed for two reasons. The first reason is to partition screen space between various applications and documents. The second reason is to enable the user to view portions of a document when the document is too large to completely fit into the window. The Windows operating system and MFC pretty much take care of the partitioning of screen space. However, if you want to enable the user to view portions of a large document, you must create scrolling windows.

Adding scroll bars to an application from scratch is a complicated task. Luckily for Visual C++ programmers, MFC handles many of the details involved in scrolling windows over documents. If you use the document/view architecture and derive your view window from MFC's CScrollView class, you get scrolling capabilities almost for free. I say "almost" because there are still a few details that you must handle. You'll learn those details in the following sections.

> If you create your application using AppWizard, you can specify that you want to use CScrollView as the base class for your view class. To do this, in the Step 6 Of 6 dialog box displayed by AppWizard, select your view window in the class list and then select CScrollView in the Base Class dialog box, as shown in Figure 6.7.

Fig. 6.7

You can create a scrolling window from within AppWizard.

Building the Scroll Application

In this section, you will build a sample program called Scroll, to experiment with a scrolling window. When Scroll first runs, it displays five lines of text. Each time you click the window with your left mouse button, five lines of text are added to the display. When you get more lines of text than fit in the window, a vertical scroll bar appears enabling you to scroll to the parts of the documents that you can't see.

As usual, building the application starts with AppWizard. Choose File, New, and select the Projects tab. Fill in the project name as Scroll and fill in an appropriate directory for the project files. Make sure that MFC AppWizard (exe) is selected. Click OK.

Complete the AppWizard steps, selecting the following options:

Step 1: Single document

Step 2: Default settings

Step 3: Default settings

Step 4: Unselect all check boxes

Step 5: Default settings

Step 6: Select CScrollView from the Base Class drop-down box as in Figure 6.7.

The New Project Information dialog box should resemble Figure 6.8. Click OK to create the project.

Fig. 6.8

Create a scroll application with AppWizard.

This application generates very simple lines of text. You only need to keep track of the number of lines that are in the scrolling view at the moment. To do this, add a variable to the document class by following these steps:

1. In ClassView, expand the classes, then right-click CScrollDoc.

2. Choose Add Member Variable from the shortcut menu

3. Fill in int as the variable type

4. Fill in m_NumLines as the variable declaration

Variables associated with a document are initialized in OnNewDocument(). In ClassView, expand CScrollDoc, then double-click OnNewDocument() to expand it. Replace the TODO comments with this line of code:

         m_NumLines = 5;

To arrange for this variable to be saved with the document and restored when the document is loaded, you must serialize it. Edit CScrollDoc::Serialize() as shown in Listing 6.9.

Listing 6.9—CScrollDoc::Serialize()

    void CScrollDoc::Serialize(CArchive& ar)

    {

         if (ar.IsStoring())

         {

              ar << m_NumLines;

         }

         else

         {

              ar >> m_NumLines;

         }

    }

Now all you need to do is use m_NumLines to draw the appropriate number of lines. Expand the view class, CMyScrollView, in ClassView, then double-click OnDraw(). Edit it until it is the same as Listing 6.10. This is very similar to the ShowFonts() code from the Paint1 application earlier in this chapter.

Listing 6.10 CMyScrollView::OnDraw()

    void CMyScrollView::OnDraw(CDC* pDC)

    {

         CScrollDoc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         // get the number of lines from the document

         int numLines = pDoc->m_NumLines;

         // Initialize a LOGFONT structure for the fonts.

         LOGFONT logFont;

        logFont.lfHeight = 24;

        logFont.lfWidth = 0;

        logFont.lfEscapement = 0;

        logFont.lfOrientation = 0;

        logFont.lfWeight = FW_NORMAL;

        logFont.lfItalic = 0;

        logFont.lfUnderline = 0;

        logFont.lfStrikeOut = 0;

        logFont.lfCharSet = ANSI_CHARSET;

        logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;

        logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;

        logFont.lfQuality = PROOF_QUALITY;

>

        logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;

         strcpy(logFont.lfFaceName, "Times New Roman");

         // Create a new font and select it into the DC.

        CFont* font = new CFont();

         font->CreateFontIndirect(&logFont);

         CFont* oldFont = pDC->SelectObject(font);

         // Initialize the position of text in the window.

         UINT position = 0;

         // Create and display eight example lines.

         for (int x=0; x<numLines; ++x)

         {

              // Create the string to display.

              char s[25];

              wsprintf(s, "This is line #%d", x+1);

              // Print text with the new font.

              pDC->TextOut(20, position, s);

              position += logFont.lfHeight;

         }

         // Restore the old font to the DC, and

         // delete the font the program created.

         pDC->SelectObject(oldFont);

         delete font;

    }

Build and run the Scroll application, and you should see a display like Figure 6.9. There are no scroll bars, because all the lines fit in the window.

Fig. 6.9

At first, the Scroll application displays five lines of text and no scroll bars.

To increase the number of lines whenever the user clicks the window, you need to add a message handler to handle left mouse clicks, then write the code for the handler. Right-click CMyScrollView in ClassView, and choose Add Windows Message Handler. Double-click WM_LBUTTONDOWN to add a handler, then click the Edit Existing button to change the code. Listing 6.11 shows the completed handler: it simply increases the number of lines, then calls Invalidate() to force a redraw. Like so many message handlers, it finishes by passing the work on to the base class version of this function.

Listing 6.11 CMyScrollView::OnLButtonDown()

    void CMyScrollView::OnLButtonDown(UINT nFlags, CPoint point)

    {

         CScrollDoc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         // Increase number of lines to display.

         pDoc->m_NumLines += 5;

         // Redraw the window.

         Invalidate();

         CScrollView::OnLButtonDown(nFlags, point);

    }

So that you can watch scroll bars disappear as well as appear, why not implement a way for the user to decrease the number of lines in the window? If left-clicking increase the number of lines, it makes sense that right-clicking would decrease it. Add a handler for WM_RBUTTONDOWN just as you did for WM_LBUTTONDOWN, and edit it until it is just like Listing 6.12. This function is a little more complicated, because it ensures that the number of lines is never negative.

Listing 6.12 CMyScrollView::OnRButtonDown()

    void CMyScrollView::OnRButtonDown(UINT nFlags, CPoint point)

    {

         CScrollDoc* pDoc = GetDocument();

         ASSERT_VALID(pDoc);

         // Decrease number of lines to display.

         pDoc->m_NumLines -= 5;

         if (pDoc->m_NumLines < 0)

         {

              pDoc->m_NumLines = 0;

         }

         // Redraw the window.

         Invalidate();

         CScrollView::OnRButtonDown(nFlags, point);

    }

If you build and run Scroll now, and click the window, you can increase the number of lines, but scroll bars do not appear. You need to add some lines to OnDraw() to make that happen. Before you do, it would be a good idea to review the way that scroll bars work. There are three places that you can click on a vertical scroll bar: the thumb (some people call it the elevator), above the thumb, or below it. Clicking the thumb does nothing, but you can click and hold to drag it up or down. Clicking above it moves you one page (screenful) up within the data. Clicking below it moves you one page down. What’s more, the size of the thumb is a visual representation of the size of a page in proportion to the entire document. Clicking the up arrow at the top of the scroll bar moves you up one line in the document, and clicking the down arrow at the bottom moves you down one line.

What all this means is that the code that draws the scroll bar and handles the clicks needs to know the size of the entire document, the size of a page, and the size of a line. You don’t have to write code to draw scroll bars, or code to handle clicks on the scroll bar, but you do have to pass along some information about the size of the document and the current view. The lines of code you need to add to OnDraw() are in Listing 6.13; add them after the for loop and before the old font is selected back into the DC.

Listing 6.12 Lines to add to OnDraw()

         // Calculate the document size.

         CSize docSize(100, numLines*logFont.lfHeight);

         // Calculate the page size.

            CRect rect;

         GetClientRect(&rect);

         CSize pageSize(rect.right, rect.bottom);

         // Calculate the line size.

         CSize lineSize(0, logFont.lfHeight);

         // Adjust the scrollers.

         SetScrollSizes(MM_TEXT, docSize, pageSize, lineSize);

This new code must determine the document, page, and line sizes. The document size is the width and height of the screen area that could hold the entire document. This is calculated using the number of lines in the entire document, and the height of a line. (CSize is an MFC class that was created especially for storing the widths and heights of objects.) The page size is simply the size of the client rectangle of this view, and the line size is the height of the font. By setting the horizontal component of the line size to zero, you prevent horizontal scrolling.

These three sizes must be passed along in order to implement scrolling. Simply call SetScrollSizes(), which takes the mapping mode, document size, page size, and line size. MFC will set the scroll bars properly for any document, and handle the user's interaction with the scroll bars.

Build and run Scroll again, and generate some more lines. You should see a scroll bar like the one in Figure 6.10. Add even more lines and you should see the thumb shrink as the document size grows. Finally, resize the application horizontally so that the text will not all fit, and notice how no horizontal scroll bars appear, because you set the horizontal line size to zero.

Fig. 6.10

After displaying more lines than fit in the window, the vertical scroll bar appears.

### From Here...

You're really starting to master Visual C++ now. Take some time at this point to look over the CDC class, and the several classes derived from CDC, in your Visual C++ online documentation. You'll discover a wealth of member functions that you can use to create displays for your views. To learn more about related topics, check out the following chapters:

- Chapter 4, "Messages and Commands," describes MFC's message-mapping system, which enables you to respond to Windows messages.
- Chapter 17, "Building an ActiveX Control," uses many of these drawing techniques to build the user interface of an ActiveX control.
- Appendix B, "Working with Developer Studio," tells you what you need to know to create your own projects using Visual C++'s development tools.

© 1997, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.