| Tenouk C & C++ | MFC Home | Basic Event Handling, Mapping Modes, and a Scrolling View 2 | GDI, Colors & Fonts 1 | Download | Site Index |


 

 

 

 

 

Module 3b:

Basic Event Handling, Mapping Modes, and a Scrolling View 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 MYMFC2 Example: Converting to the MM_HIMETRIC Mapping Mode

  2. A Scrolling View Window

  3. A Window Is Larger than What You See

  4. Scroll Bars

  5. Scrolling Alternatives

  6. The OnInitialUpdate() Function

  7. Accepting Keyboard Input

  8. The MYMFC3 Example - Scrolling

  9. Other Windows Messages

  10. The WM_CREATE Message

  11. The WM_CLOSE Message

  12. The WM_QUERYENDSESSION Message

  13. The WM_DESTROY Message

  14. The WM_NCDESTROY Message

 

The MYMFC2 Example: Converting to the MM_HIMETRIC Mapping Mode

 

MYMFC2 is MYMFC1 converted to MM_HIMETRIC coordinates. Like MYMFC1, MYMFC2 performs a hit-test so that the ellipse changes color only when you click inside the bounding rectangle. Use ClassWizard to override the virtual OnPrepareDC() function. ClassWizard can override virtual functions for selected MFC base classes, including CView. It generates the correct function prototype in the class's header file and a skeleton function in the CPP file. Select the class name CMymfc1View in the Object IDs list, and then double-click on the OnPrepareDC() function in the Messages list.

 

Edit the function as shown here:

 

Using the ClassWizard to override the virtual OnPrepareDC() function.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

Using ClassWizard to override the virtual OnPrepareDC() function

 

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

 

 

 

Enter the OnPrepareDC() code.

 

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

{

    pDC->SetMapMode(MM_HIMETRIC);

    CView::OnPrepareDC(pDC, pInfo);

}

 

MFC, Visual C++ code segment

 

Listing 4.

 

The application framework calls the virtual OnPrepareDC() function just before it calls OnDraw().

Edit the view class constructor. You must change the coordinate values for the ellipse rectangle. That rectangle is now 4-by-4 centimeters instead of 200-by-200 pixels. Note that the y value must be negative; otherwise, the ellipse will be drawn on the "virtual screen" right above your monitor! Change the values as shown here:

 

CMymfc1View::CMymfc1View() : m_rectEllipse(0, 0, 4000, -4000)

{

    m_nColor = GRAY_BRUSH;

}

 

MFC, Visual C++ code segment

 

Listing 5.

 

Edit the OnLButtonDown() function. This function must now convert the ellipse rectangle to device coordinates in order to do the hit-test. Change the function as shown in the following code:

 

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

{

    CClientDC dc(this);

    OnPrepareDC(&dc);

    CRect rectDevice = m_rectEllipse;

    dc.LPtoDP(rectDevice);

    if (rectDevice.PtInRect(point)) {

        if (m_nColor == GRAY_BRUSH) {

            m_nColor = WHITE_BRUSH;

        }

        else {

            m_nColor = GRAY_BRUSH;

        }

        InvalidateRect(rectDevice);

    }

}

 

MFC, Visual C++ code segment

 

Listing 6.

 

Build and run the MYMFC2 program. The output should look similar to the output from MYMFC1, except that the ellipse size will be different.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

MYMFC2 program output

 

Figure 15: MYMFC2 program output.

 

If you try using Print Preview again, the ellipse should appear much larger than it did in MYMFC1.

 

Better print preview of the MYMFC1

 

Figure 16: Better print preview of the MYMFC1.

 

A Scrolling View Windows

 

As the lack of scroll bars in MYMFC1 and MYMFC2 indicates, the MFC CView class, the base class of CMymfc2View, doesn't directly support scrolling. Another MFC library class, CScrollView, does support scrolling. CScrollView is derived from CView. We'll create a new program, MYMFC3 that uses CScrollView in place of CView. All the coordinate conversion code you added in MYMFC2 sets you up for scrolling. The CScrollView class supports scrolling from the scroll bars but not from the keyboard. It's easy enough to add keyboard scrolling, so we'll do it.

 

A Window Is Larger than What You See

 

If you use the mouse to shrink the size of an ordinary window, the contents of the window remain anchored at the top left of the window, and items at the bottom and/or on the right of the window disappear. When you expand the window, the items reappear. You can correctly conclude that a window is larger than the viewport that you see on the screen. The viewport doesn't have to be anchored at the top left of the window area, however. Through the use of the CWnd functions ScrollWindow() and SetWindowOrg(), the CScrollView class allows you to move the viewport anywhere within the window, including areas above and to the left of the origin.

 

Scroll Bars

 

Microsoft Windows makes it easy to display scroll bars at the edges of a window, but Windows by itself doesn't make any attempt to connect those scroll bars to their window. That's where the CScrollView class fits in. CScrollView member functions process the WM_HSCROLL and WM_VSCROLL messages sent by the scroll bars to the view. Those functions move the viewport within the window and do all the necessary housekeeping.

 

Scrolling Alternatives

 

The CScrollView class supports a particular kind of scrolling that involves one big window and a small viewport. Each item is assigned a unique position in this big window. If, for example, you have 10,000 address lines to display, instead of having a window 10,000 lines long, you probably want a smaller window with scrolling logic that selects only as many lines as the screen can display. In that case, you should write your own scrolling view class derived from CView.

Microsoft Windows NT uses 32-bit numbers for logical coordinates, so your logical coordinate space is almost unlimited. Microsoft Windows 95, however, still has some 16-bit components, so it uses 16-bit numbers for logical coordinates, limiting values to the range -32,768 to 32,767. Scroll bars send messages with 16-bit values in both operating systems. With these facts in mind, you probably want to write code to the lowest common denominator, which is Windows 95.

 

The OnInitialUpdate() Function

 

You'll be seeing more of the OnInitialUpdate() function when you study the document-view architecture. The virtual OnInitialUpdate() function is important here because it is the first function called by the framework after your view window is fully created. The framework calls OnInitialUpdate() before it calls OnDraw() for the first time, so OnInitialUpdate() is the natural place for setting the logical size and mapping mode for a scrolling view. You set these parameters with a call to the CScrollView::SetScrollSizes function.

 

Accepting Keyboard Input

 

Keyboard input is really a two-step process. Windows sends WM_KEYDOWN and WM_KEYUP messages, with virtual key codes, to a window, but before they get to the window they are translated. If an ANSI character is typed (resulting in a WM_KEYDOWN message), the translation function checks the keyboard shift status and then sends a WM_CHAR message with the proper code, either uppercase or lowercase. Cursor keys and function keys don't have codes, so there's no translation to do. The window gets only the WM_KEYDOWN and WM_KEYUP messages. You can use ClassWizard to map all these messages to your view. If you're expecting characters, map WM_CHAR; if you're expecting other keystrokes, map WM_KEYDOWN. The MFC library neatly supplies the character code or virtual key code as a handler function parameter.

 

The MYMFC3 Example - Scrolling

 

The goal of MYMFC3 is to make a logical window 20 centimeters wide by 30 centimeters high. The program draws the same ellipse that it drew in the MYMFC2 project. You could edit the MYMFC2 source files to convert the CView base class to a CScrollView base class, but it's easier to start over with AppWizard. AppWizard generates the OnInitialUpdate() override function for you. Here are the steps:

 

Run AppWizard to create MYMFC3.Use AppWizard to generate a program named MYMFC3 in the mfcproject\mymfc3 subdirectory. In AppWizard Step 6, set the CMymfc3View base class to CScrollView, as shown here.

 

 

 

AppWizard Step 6, setting the CMymfc3View base class to CScrollView

 

Figure 17: AppWizard Step 6, setting the CMymfc3View base class to CScrollView.

 

MYMFC3 project summary

 

Figure 18: MYMFC3 project summary.

 

Add the m_rectEllipse and m_nColor data members in mymfc3View.h. Insert the following code by right-clicking the CMymfc3View class in the Workspace window or by typing inside the CMymfc3View.h class declaration:

 

private:

    CRect m_rectEllipse;

    int m_nColor;

 

Adding private member variable

 

Figure 19: Adding private member variable.

 

These are the same data members that were added in the MYMFC1 and MYMFC2 projects.

 

Modify the AppWizard-generated OnInitialUpdate() function. Edit OnInitialUpdate() in mymfc3View.cpp as shown here:

 

void CMymfc3View::OnInitialUpdate()

{

    CScrollView::OnInitialUpdate();

    // 20 by 30 cm

    CSize sizeTotal(20000, 30000);

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

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

    SetScrollSizes(MM_HIMETRIC, sizeTotal, sizePage, sizeLine);

}

 

MFC, Visual C++ code segment

 

Listing 7.

 

Use ClassWizard to add a message handler for the WM_KEYDOWN message.

 

Using ClassWizard to add a message handler for the WM_KEYDOWN message

 

Figure 20: Using ClassWizard to add a message handler for the WM_KEYDOWN message.

 

ClassWizard generates the member function OnKeyDown() along with the necessary message map entries and prototypes. Edit the code as follows:

 

void CMymfc3View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)

{

    switch (nChar) {

    case VK_HOME:

        OnVScroll(SB_TOP, 0, NULL);

        OnHScroll(SB_LEFT, 0, NULL);

        break;

    case VK_END:

        OnVScroll(SB_BOTTOM, 0, NULL);

        OnHScroll(SB_RIGHT, 0, NULL);

        break;

    case VK_UP:

        OnVScroll(SB_LINEUP, 0, NULL);

        break;

    case VK_DOWN:

        OnVScroll(SB_LINEDOWN, 0, NULL);

        break;

    case VK_PRIOR:

        OnVScroll(SB_PAGEUP, 0, NULL);

        break;

    case VK_NEXT:

        OnVScroll(SB_PAGEDOWN, 0, NULL);

        break;

    case VK_LEFT:

        OnHScroll(SB_LINELEFT, 0, NULL);

        break;

    case VK_RIGHT:

        OnHScroll(SB_LINERIGHT, 0, NULL);

        break;

    default:

        break;

    }

}

 

MFC, Visual C++ code segment

 

Listing 8.

 

 

Edit the constructor and the OnDraw() function. Change the AppWizard-generated constructor and the OnDraw() function in mymfc3View.cpp as follows:

 

CMymfc3View::CMymfc3View() : m_rectEllipse(0, 0, 4000, -4000)

{

    m_nColor = GRAY_BRUSH;

}

.

.

.

void CMymfc3View::OnDraw(CDC* pDC)

{

    pDC->SelectStockObject(m_nColor);

    pDC->Ellipse(m_rectEllipse);

}

 

MFC, Visual C++ code segment

 

Listing 9.

 

MFC, Visual C++ code segment

 

Listing 10.

 

These functions are identical to those used in the MYMFC1 and MYMFC2 projects. Map the WM_LBUTTONDOWN message and edit the message handler by clicking the Edit Code button.

 

Using ClassWizard to map the WM_LBUTTONDOWN message

 

Figure 21: Using ClassWizard to map the WM_LBUTTONDOWN message when left mouse button is clicked.

 

Make the following changes to the ClassWizard-generated code:

 

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

{

    CClientDC dc(this);

    OnPrepareDC(&dc);

    CRect rectDevice = m_rectEllipse;

    dc.LPtoDP(rectDevice);

    if (rectDevice.PtInRect(point)) {

        if (m_nColor == GRAY_BRUSH) {

            m_nColor = WHITE_BRUSH;

        }

        else {

            m_nColor = GRAY_BRUSH;

        }

        InvalidateRect(rectDevice);

    }

}

 

MFC, Visual C++ code segment

 

Listing 11.

 

This function is identical to the OnLButtonDown() handler in the MYMFC2 project. It calls OnPrepareDC() as before, but there is something different. The CMymfc2View class doesn't have an overridden OnPrepareDC() function, so the call goes to CScrollView::OnPrepareDC. That function sets the mapping mode based on the first parameter to SetScrollSizes(), and it sets the window origin based on the current scroll position. Even if your scroll view used the MM_TEXT mapping mode, you'd still need the coordinate conversion logic to adjust for the origin offset.

 

Build and run the MYMFC3 program. Check to be sure the mouse hit logic is working even if the circle is scrolled partially out of the window (use the horizontal and vertical scroll bar). Also check the keyboard logic. The output should look like this.

 

MYMFC3 program output with horizontal and vertical scroll bar

 

Figure 22: MYMFC3 program output with horizontal and vertical scroll bar.

 

Other Windows Messages

 

The MFC library directly supports hundreds of Windows message-handling functions. In addition, you can define your own messages. You will see plenty of message-handling examples in later chapters, including handlers for menu items, child window controls, and so forth. In the meantime, five special Windows messages deserve special attention: WM_CREATE, WM_CLOSE, WM_QUERYENDSESSION, WM_DESTROY, and WM_NCDESTROY.

 

The WM_CREATE Message

 

This is the first message that Windows sends to a view. It is sent when the window's Create function is called by the framework, so the window creation is not finished and the window is not visible. Therefore, your OnCreate() handler cannot call Windows functions that depend on the window being completely alive. You can call such functions in an overridden OnInitialUpdate() function, but you must be aware that in an SDI application OnInitialUpdate() can be called more than once in a view's lifetime.

 

The WM_CLOSE Message

 

Windows sends the WM_CLOSE message when the user closes a window from the system menu and when a parent window is closed. If you implement the OnClose() message map function in your derived view class, you can control the closing process. If, for example, you need to prompt the user to save changes to a file, you do it in OnClose(). Only when you have determined that it is safe to close the window do you call the base class OnClose() function, which continues the close process. The view object and the corresponding window are both still active. When you're using the full application framework, you probably won't use the WM_CLOSE message handler. You can override the CDocument::SaveModified virtual function instead, as part of the application framework's highly structured program exit procedure.

 

The WM_QUERYENDSESSION Message

 

Windows sends the WM_QUERYENDSESSION message to all running applications when the user exits Windows. The OnQueryEndSession() message map function handles it. If you write a handler for WM_CLOSE, write one for WM_QUERYENDSESSION too.

 

The WM_DESTROY Message

 

Windows sends this message after the WM_CLOSE message and the OnDestroy() message map function handles it. When your program receives this message, it should assume that the view window is no longer visible on the screen but that it is still active and its child windows are still active. Use this message handler to do cleanup that depends on the existence of the underlying window. Be sure to call the base class OnDestroy() function. You cannot "abort" the window destruction process in your view's OnDestroy() function. OnClose() is the place to do that.

 

The WM_NCDESTROY Message

 

This is the last message that Windows sends when the window is being destroyed. All child windows have already been destroyed. You can do final processing in OnNcDestroy() that doesn't depend on a window being active. Be sure to call the base class OnNcDestroy() function. Do not try to destroy a dynamically allocated window object in OnNcDestroy(). That job is reserved for a special CWnd virtual function, PostNcDestroy(), that the base class OnNcDestroy() calls. MFC Technical Note #17 in the online documentation gives hints on when it's appropriate to destroy a window object.

 

 

 

 

 

 

 

 

 

 

 

 

 

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 | Basic Event Handling, Mapping Modes, and a Scrolling View 2 | GDI, Colors & Fonts 1 | Download | Site Index |