| Tenouk C & C++ | MFC Home | GDI, Colors & Fonts 2 | Modal Dialog & Windows Common Controls 1 | Download | Site Index |


 

 

 

 

 

 

Module 4:

The Graphics Device Interface (GDI), Colors, and Fonts 3

 

 

 

 

 

 

 

This is a continuation from the previous module... Program examples compiled using Visual C++ 6.0 (MFC 6.0) compiler on Windows XP Pro machine with Service Pack 2. Topics and sub topics for this Tutorial are listed below:

 

  1. The MYMFC5 Example

  2. The MYMFC5 Program Elements

    1.    The OnDraw() Member Function

    2.    The TraceMetrics() Helper Function

  3. The MYMFC6 Example: CScrollView Revisited

  4. The MYMFC6 Program Elements

    1.    The m_sizeEllipse and m_pointTopLeft Data Members

    2.    The m_sizeOffset Data Member

    3.    The m_bCaptured Data Member

    4.    The SetCapture() and ReleaseCapture() Functions

    5.    The SetCursor() and LoadCursor() Win32 Functions

    6.    The CScrollView::OnPrepareDC Member Function

    7.    The OnMouseMove() Coordinate Transformation Code

    8.    The OnDraw() Function

    9.    The CScrollView SetScaleToFitSize() Mode

  5. Using the Logical Twips Mapping Mode in a Scrolling View

 

 

The MYMFC5 Project Example

 

This program is similar to MYMFC4 except that it shows multiple fonts. The mapping mode is MM_ANISOTROPIC, with the scale dependent on the window size. The characters change size along with the window. This program effectively shows off some TrueType fonts and contrasts them with the old-style fonts. Here are the steps for building the application:

 

Run AppWizard to generate the MYMFC5 project. The options and the default class names are shown here and it is similar to the MYMFC4.

 

MYMFC5 project summary

 

Figure 10: MYMFC5 project summary.

 

Use ClassWizard to override the OnPrepareDC() function in the CMymfc5View class. Edit the code in mymfc5View.cpp as shown below.

 

Using ClassWizard to override the OnPrepareDC() function

 

Figure 11: Using ClassWizard to override the OnPrepareDC() function.

 

void CMymfc5View::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)

{

    CRect clientRect;

 

    GetClientRect(clientRect);

    pDC->SetMapMode(MM_ANISOTROPIC); // +y = down

    pDC->SetWindowExt(400, 450);

    pDC->SetViewportExt(clientRect.right, clientRect.bottom);

    pDC->SetViewportOrg(0, 0);

}

 

MFC, Visual C++ code segment

 

Listing 4.

 

Add a private TraceMetrics() helper function to the view class. Add the following prototype in mymfc5View.h:

 

Adding a private TraceMetrics() helper function

 

Figure 12: Adding a private TraceMetrics() helper function.

 

private:

    void TraceMetrics(CDC* pDC);

 

Then add the function itself in mymfc5View.cpp:

 

void CMymfc5View::TraceMetrics(CDC* pDC)

{

    TEXTMETRIC tm;

    char       szFaceName[100];

 

    pDC->GetTextMetrics(&tm);

    pDC->GetTextFace(99, szFaceName);

    TRACE("font = %s, tmHeight = %d, tmInternalLeading = %d,"

          " tmExternalLeading = %d\n", szFaceName, tm.tmHeight,

          tm.tmInternalLeading, tm.tmExternalLeading);

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

MFC, Visual C++ code segment

 

Listing 5.

 

Edit the OnDraw() function in mymfc5View.cpp. AppWizard always generates a skeleton OnDraw() function for your view class. Find the function, and edit the code as follows:

void CMymfc5View::OnDraw(CDC* pDC)

{

    CFont fontTest1, fontTest2, fontTest3, fontTest4;

 

    fontTest1.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,

                         ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                         CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                         DEFAULT_PITCH | FF_SWISS, "Arial");

    CFont* pOldFont = pDC->SelectObject(&fontTest1);

    TraceMetrics(pDC);

    pDC->TextOut(0, 0, "This is Arial, default width");

 

    fontTest2.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,

                         ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                         CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                         DEFAULT_PITCH | FF_MODERN, "Courier");

                         // not TrueType

    pDC->SelectObject(&fontTest2);

    TraceMetrics(pDC);

    pDC->TextOut(0, 100, "This is Courier, default width");

 

    fontTest3.CreateFont(50, 10, 0, 0, 400, FALSE, FALSE, 0,

                         ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                         CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                         DEFAULT_PITCH | FF_ROMAN, NULL);

    pDC->SelectObject(&fontTest3);

    TraceMetrics(pDC);

    pDC->TextOut(0, 200, "This is generic Roman, variable width");

 

    fontTest4.CreateFont(50, 0, 0, 0, 400, FALSE, FALSE, 0,

                         ANSI_CHARSET, OUT_DEFAULT_PRECIS,

                         CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                         DEFAULT_PITCH | FF_MODERN, "LinePrinter");

    pDC->SelectObject(&fontTest4);

    TraceMetrics(pDC);

    pDC->TextOut(0, 300, "This is LinePrinter, default width");

    pDC->SelectObject(pOldFont);

}

 

MFC, Visual C++ code segment

 

Listing 6.

 

Build and run the MYMFC5 program. Run the program from the debugger to see the TRACE output. The program's output window is shown here.

 

MYMFC5 program output

 

Figure 13: MYMFC5 program output.

 

Resize the window to make it smaller, and watch the font sizes change. Compare this window with the previous one.

 

MYMFC5 program output when the window is resized

 

Figure 14: MYMFC5 program output when the window is resized.

 

If you continue to downsize the window, notice how the Courier font stops shrinking after a certain size and how the Roman font width changes.

 

The MYMFC5 Program Elements

 

Following is a discussion of the important elements in the MYMFC5 example.

 

The OnDraw() Member Function

 

The OnDraw() function displays character strings in four fonts, as follows:

 

Fonts

Description

fontTest1

The TrueType font Arial with default width selection.

fontTest2

The old-style font Courier with default width selection. Notice how jagged the font appears in larger sizes.

fontTest3

The generic Roman font for which Windows supplies the TrueType font Times New Roman with programmed width selection. The width is tied to the horizontal window scale, so the font stretches to fit the window.

fontTest4

The LinePrinter font is specified, but because this is not a Windows font for the display, the font engine falls back on the FF_MODERN specification and chooses the TrueType Courier New font.

 

Table 5.

 

The TraceMetrics() Helper Function

 

The TraceMetrics() helper function calls CDC::GetTextMetrics and CDC::GetTextFace to get the current font's parameters, which it prints in the Debug window.

 

The MYMFC6 Project Example: CScrollView Revisited

 

You saw the CScrollView class in previous Module. The MYMFC6 program allows the user to move an ellipse with a mouse by "capturing" the mouse, using a scrolling window with the MM_LOENGLISH mapping mode. Keyboard scrolling is left out, but you can add it by borrowing the OnKeyDown() member function from MYMFC3 example in previous Module. Instead of a stock brush, we'll use a pattern brush for the ellipse: a real GDI object. There's one complication with pattern brushes: you must reset the origin as the window scrolls; otherwise, strips of the pattern don't line up and the effect is ugly. As with the MYMFC3 program, this example involves a view class derived from CScrollView. Here are the steps to create the application:

 

Run AppWizard to generate the MYMFC6 project. Be sure to set the view base class to CScrollView in step 6. The options and the default class names are shown here.

 

Step 6 of the MYMFC6 project, using CScrollView as the View base class

 

Figure 15: Step 6 of the MYMFC6 project, using CScrollView as the View base class.

 

MYMFC6 project summary

 

Figure 16: MYMFC6 project summary.

 

Edit the CMymfc6View class header in the file mymfc6View.h. Add the following lines in the class CMymfc6View declaration:

 

private:

    const CSize m_sizeEllipse;

    CPoint m_pointTopLeft; // logical, top left of ellipse rectangle

    CSize  m_sizeOffset;   // device, from rect top left

                      

    // to capture point

    BOOL   m_bCaptured;

 

MFC, Visual C++ code segment

 

Listing 7.

 

Use ClassWizard to add three message handlers to the CMymfc6View class. Add the message handlers as follows:

 

Message

Member Function

WM_LBUTTONDOWN

OnLButtonDown()

WM_LBUTTONUP

OnLButtonUp()

WM_MOUSEMOVE

OnMouseMove()

 

Table 6.

 

Adding three message handlers using ClassWizard

 

Figure 17: Adding three message handlers using ClassWizard.

 

Edit the CMymfc6View message handler functions by clicking the Edit Code button. ClassWizard generated the skeletons for the functions listed in the preceding step. Find the functions in mymfc6View.cpp, and code them as follows.

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

{

    CRect rectEllipse(m_pointTopLeft, m_sizeEllipse); // still logical

    CRgn  circle;

 

    CClientDC dc(this);

    OnPrepareDC(&dc);

    dc.LPtoDP(rectEllipse); // Now it's in device coordinates

    circle.CreateEllipticRgnIndirect(rectEllipse);

    if (circle.PtInRegion(point)) {

        // Capturing the mouse ensures subsequent LButtonUp message

        SetCapture();

        m_bCaptured = TRUE;

        CPoint pointTopLeft(m_pointTopLeft);

        dc.LPtoDP(&pointTopLeft);

        m_sizeOffset = point - pointTopLeft; // device coordinates

        // New mouse cursor is active while mouse is captured

        ::SetCursor(::LoadCursor(NULL, IDC_CROSS));

    }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

-----------------------------------------------------------------------------------------------------------------------------------------------

 

MFC, Visual C++ code segment

 

Listing 8.

void CMymfc6View::OnLButtonUp(UINT nFlags, CPoint point)

{

    if (m_bCaptured)

    {

        ::ReleaseCapture();

        m_bCaptured = FALSE;

    }

}

 

void CMymfc6View::OnMouseMove(UINT nFlags, CPoint point)

{

    if (m_bCaptured)

    {

        CClientDC dc(this);

        OnPrepareDC(&dc);

        CRect rectOld(m_pointTopLeft, m_sizeEllipse);

        dc.LPtoDP(rectOld);

        InvalidateRect(rectOld, TRUE);

        m_pointTopLeft = point - m_sizeOffset;

        dc.DPtoLP(&m_pointTopLeft);

        CRect rectNew(m_pointTopLeft, m_sizeEllipse);

        dc.LPtoDP(rectNew);

        InvalidateRect(rectNew, TRUE);

    }

}

 

MFC, Visual C++ code segment

 

Listing 9.

 

Edit the CMymfc6View constructor, the OnDraw() function, and the OnInitialUpdate() function. AppWizard generated these skeleton functions. Find them in mymfc6View.cpp, and code them as follows:

 

CMymfc6View::CMymfc6View() : m_sizeEllipse(100, -100),m_pointTopLeft(0, 0), m_sizeOffset(0, 0)

{

    m_bCaptured = FALSE;

}

 

MFC, Visual C++ code segment

 

Listing 10.

 

void CMymfc6View::OnDraw(CDC* pDC)

{

    CBrush brushHatch(HS_DIAGCROSS, RGB(255, 0, 0));

    // logical (0, 0)

    CPoint point(0, 0);

 

    // In device coordinates, align the brush with the window origin

    pDC->LPtoDP(&point);

    pDC->SetBrushOrg(point);

 

    pDC->SelectObject(&brushHatch);

    pDC->Ellipse(CRect(m_pointTopLeft, m_sizeEllipse));

    pDC->SelectStockObject(BLACK_BRUSH); // Deselect brushHatch

    pDC->Rectangle(CRect(100, -100, 200, -200)); // Test invalid rect

}

 

MFC, Visual C++ code segment

 

Listing 11.

 

void CMymfc6View::OnInitialUpdate()

{

    CScrollView::OnInitialUpdate();

 

    CSize sizeTotal(800, 1050); // 8-by-10.5 inches

    CSize sizePage(sizeTotal.cx / 2, sizeTotal.cy / 2);

    CSize sizeLine(sizeTotal.cx / 50, sizeTotal.cy / 50);

    SetScrollSizes(MM_LOENGLISH, sizeTotal, sizePage, sizeLine);

}

 

MFC, Visual C++ code segment

 

Listing 12.

 

Build and run the MYMFC6 program. The program allows an ellipse to be dragged with the mouse, and it allows the window to be scrolled through. The program's window should look like the one shown here. As you move the ellipse, observe the black rectangle. You should be able to see the effects of invalidating the rectangle.

 

MYMFC6 program output with horizontal and vertical scroll bar.

 

Figure 18: MYMFC6 program output with horizontal and vertical scroll bar.

 

MYMFC6 program output, dragging an ellipse

Figure 19: MYMFC6 program output, dragging an ellipse.

 

 

The MYMFC6 Program Elements

 

Following is a discussion of the important elements in the MYMFC6 example.

 

The m_sizeEllipse and m_pointTopLeft Data Members

 

Rather than store the ellipse's bounding rectangle as a single CRect object, the program separately stores its size (m_sizeEllipse) and the position of its top left corner (m_pointTopLeft). To move the ellipse, the program merely recalculates m_pointTopLeft, and any round-off errors in the calculation won't affect the size of the ellipse.

 

The m_sizeOffset Data Member

 

When OnMouseMove() moves the ellipse, the relative position of the mouse within the ellipse must be the same as it was when the user first pressed the left mouse button. The m_sizeOffset object stores this original offset of the mouse from the top left corner of the ellipse rectangle.

 

The m_bCaptured Data Member

 

The m_bCaptured Boolean variable is set to TRUE when mouse tracking is in progress.

 

The SetCapture() and ReleaseCapture() Functions

 

SetCapture() is the CWnd member function that "captures" the mouse, such that mouse movement messages are sent to this window even if the mouse cursor is outside the window. An unfortunate side effect of this function is that the ellipse can be moved outside the window and "lost." A desirable and necessary effect is that all subsequent mouse messages are sent to the window, including the WM_LBUTTONUP message, which would otherwise be lost. The Win32 ReleaseCapture() function turns off mouse capture.

 

The SetCursor() and LoadCursor() Win32 Functions

 

The MFC library does not "wrap" some Win32 functions. By convention, we use the C++ scope resolution operator (::) when calling Win32 functions directly. In this case, there is no potential for conflict with a CView member function, but you can deliberately choose to call a Win32 function in place of a class member function with the same name. In that case, the :: operator ensures that you call the globally scoped Win32 function. When the first parameter is NULL, the LoadCursor() function creates a cursor resource from the specified predefined mouse cursor that Windows uses. The SetCursor() function activates the specified cursor resource. This cursor remains active as long as the mouse is captured.

 

The CScrollView::OnPrepareDC Member Function

 

The CView class has a virtual OnPrepareDC() function that does nothing. The CScrollView class implements the function for the purpose of setting the view's mapping mode and origin, based on the parameters that you passed to SetScrollSizes() in OnCreate(). The application framework calls OnPrepareDC() for you prior to calling OnDraw(), so you don't need to worry about it. You must call OnPrepareDC() yourself in any other message handler function that uses the view's device context, such as OnLButtonDown() and OnMouseMove().

 

The OnMouseMove() Coordinate Transformation Code

 

As you can see, this function contains several translation statements. The logic can be summarized by the following steps:

 

  1. Construct the previous ellipse rectangle and convert it from logical to device coordinates.

  2. Invalidate the previous rectangle.

  3. Update the top left coordinate of the ellipse rectangle.

  4. Construct the new rectangle and convert it to device coordinates.

  5. Invalidate the new rectangle.

 

The function calls InvalidateRect() twice. Windows "saves up" the two invalid rectangles and computes a new invalid rectangle that is the union of the two, intersected with the client rectangle.

 

The OnDraw() Function

 

The SetBrushOrg() call is necessary to ensure that all of the ellipse's interior pattern lines up when the user scrolls through the view. The brush is aligned with a reference point, which is at the top left of the logical window, converted to device coordinates. This is a notable exception to the rule that CDC member functions require logical coordinates.

 

The CScrollView SetScaleToFitSize() Mode

 

The CScrollView class has a stretch-to-fit mode that displays the entire scrollable area in the view window. The Windows MM_ANISOTROPIC mapping mode comes into play, with one restriction: positive y values always increase in the down direction, as in MM_TEXT mode. To use the stretch-to-fit mode, make the following call in your view's function in place of the call to SetScrollSizes():

 

SetScaleToFitSize(sizeTotal);

 

You can make this call in response to a Shrink To Fit menu command. Thus, the display can toggle between scrolling mode and shrink-to-fit mode.

 

Using the Logical Twips Mapping Mode in a Scrolling View

 

The MFC CScrollView class allows you to specify only standard mapping modes. You can use a new class, CLogScrollView to accommodate the logical twips mode.

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

  1. MSDN MFC 7.0 class library online documentation.

  2. MSDN MFC 9.0 class library online documentation - latest version.

  3. Porting & Migrating your older programs.

  4. MSDN Library

  5. DCOM at MSDN.

  6. COM+ at MSDN.

  7. COM at MSDN.

  8. Windows data type.

  9. Win32 programming Tutorial.

  10. The best of C/C++, MFC, Windows and other related books.

  11. Unicode and Multibyte character set: Story and program examples.

 

 


 

| Tenouk C & C++ | MFC Home | GDI, Colors & Fonts 2 | Modal Dialog & Windows Common Controls 1 | Download | Site Index |